├── CHANGELOG.md ├── sdk-common ├── src │ └── main │ │ └── java │ │ └── net │ │ └── aibote │ │ └── utils │ │ ├── ClientType.java │ │ ├── Base64Utils.java │ │ ├── ImageBase64Converter.java │ │ └── HttpClientUtils.java └── pom.xml ├── sdk-server ├── src │ ├── test │ │ └── java │ │ │ └── net │ │ │ └── aibote │ │ │ └── WinBotTest.java │ └── main │ │ └── java │ │ └── net │ │ └── aibote │ │ ├── handler │ │ ├── impl │ │ │ ├── WebHandler.java │ │ │ ├── WinHandler.java │ │ │ ├── AndroidHandler.java │ │ │ ├── WebClientManager.java │ │ │ ├── WinClientManager.java │ │ │ └── AndroidClientManager.java │ │ ├── AiboteChannel.java │ │ ├── ClientManager.java │ │ └── BotHandler.java │ │ ├── scripts │ │ ├── AndroidBotTest.java │ │ └── ScriptManager.java │ │ ├── server │ │ ├── impl │ │ │ ├── WinServer.java │ │ │ ├── WebServer.java │ │ │ └── AndroidServer.java │ │ └── BotServer.java │ │ ├── App.java │ │ └── codec │ │ ├── AiboteEncoder.java │ │ └── AiboteDecoder.java └── pom.xml ├── .gitignore ├── sdk-core ├── src │ └── main │ │ └── java │ │ └── net │ │ └── aibote │ │ └── sdk │ │ ├── dto │ │ ├── OCRResult.java │ │ └── Point.java │ │ ├── options │ │ ├── SubColor.java │ │ ├── Region.java │ │ ├── Mode.java │ │ └── GesturePath.java │ │ ├── Aibote.java │ │ ├── WebBot.java │ │ ├── AndroidBot.java │ │ └── WinBot.java └── pom.xml ├── README.md ├── pom.xml └── LICENSE /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1341191074/aibote4j/HEAD/CHANGELOG.md -------------------------------------------------------------------------------- /sdk-common/src/main/java/net/aibote/utils/ClientType.java: -------------------------------------------------------------------------------- 1 | package net.aibote.utils; 2 | 3 | public enum ClientType { 4 | Win, 5 | Web, 6 | Android 7 | } 8 | -------------------------------------------------------------------------------- /sdk-server/src/test/java/net/aibote/WinBotTest.java: -------------------------------------------------------------------------------- 1 | package net.aibote; 2 | 3 | public class WinBotTest { 4 | public static void main(String[] args) { 5 | System.out.println("111111"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | *.iml 4 | .settings 5 | target 6 | .classpath 7 | bin 8 | transaction-logs 9 | .project 10 | .externalToolBuilders 11 | .sts4-cache 12 | .factorypath 13 | .springBeans 14 | .bpmn 15 | .gradle -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/dto/OCRResult.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk.dto; 2 | 3 | public class OCRResult { 4 | public Point lt; 5 | public Point rt; 6 | public Point ld; 7 | public Point rd; 8 | public String word; 9 | public double rate; 10 | } 11 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/options/SubColor.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk.options; 2 | 3 | import lombok.AllArgsConstructor; 4 | 5 | /** 6 | * 用于查找颜色中的 辅助颜色 7 | */ 8 | @AllArgsConstructor 9 | public class SubColor { 10 | public int offsetX; 11 | public int offsetY; 12 | public String colorStr; 13 | } 14 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/dto/Point.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.NoArgsConstructor; 5 | 6 | @AllArgsConstructor 7 | @NoArgsConstructor 8 | public class Point { 9 | 10 | /** 11 | * x。默认0 12 | */ 13 | public int x; 14 | /** 15 | * y。默认0 16 | */ 17 | public int y; 18 | } 19 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/impl/WebHandler.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler.impl; 2 | 3 | import net.aibote.handler.BotHandler; 4 | import net.aibote.handler.ClientManager; 5 | 6 | public class WebHandler extends BotHandler { 7 | @Override 8 | public ClientManager getClientManager() { 9 | return WebClientManager.getInstance(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/impl/WinHandler.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler.impl; 2 | 3 | import net.aibote.handler.BotHandler; 4 | import net.aibote.handler.ClientManager; 5 | 6 | public class WinHandler extends BotHandler { 7 | @Override 8 | public ClientManager getClientManager() { 9 | return WinClientManager.getInstance(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/impl/AndroidHandler.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler.impl; 2 | 3 | import net.aibote.handler.BotHandler; 4 | import net.aibote.handler.ClientManager; 5 | 6 | public class AndroidHandler extends BotHandler { 7 | @Override 8 | public ClientManager getClientManager() { 9 | return AndroidClientManager.getInstance(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/options/Region.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk.options; 2 | 3 | /** 4 | * 用于描述region范围的 5 | */ 6 | public class Region { 7 | /** 8 | * 左。默认0 9 | */ 10 | public int left; 11 | /** 12 | * 上。默认0 13 | */ 14 | public int top; 15 | /** 16 | * 右。默认0 17 | */ 18 | public int right; 19 | /** 20 | * 下。默认0 21 | */ 22 | public int bottom; 23 | } 24 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/impl/WebClientManager.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler.impl; 2 | 3 | import net.aibote.handler.ClientManager; 4 | 5 | public class WebClientManager extends ClientManager { 6 | private static WebClientManager instance = new WebClientManager(); 7 | public static ClientManager getInstance() 8 | { 9 | return instance; 10 | } 11 | 12 | private WebClientManager() { } 13 | } 14 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/impl/WinClientManager.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler.impl; 2 | 3 | import net.aibote.handler.ClientManager; 4 | 5 | public class WinClientManager extends ClientManager { 6 | private static WinClientManager instance = new WinClientManager(); 7 | 8 | public static ClientManager getInstance() { 9 | return instance; 10 | } 11 | 12 | private WinClientManager() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/impl/AndroidClientManager.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler.impl; 2 | 3 | import net.aibote.handler.ClientManager; 4 | 5 | public class AndroidClientManager extends ClientManager { 6 | private static AndroidClientManager instance = new AndroidClientManager(); 7 | public static ClientManager getInstance() 8 | { 9 | return instance; 10 | } 11 | 12 | private AndroidClientManager() { } 13 | } 14 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/scripts/AndroidBotTest.java: -------------------------------------------------------------------------------- 1 | package net.aibote.scripts; 2 | 3 | import net.aibote.sdk.AndroidBot; 4 | 5 | public class AndroidBotTest extends AndroidBot { 6 | @Override 7 | public String getScriptName() { 8 | return "测试脚本"; 9 | } 10 | 11 | @Override 12 | public void doScript() { 13 | this.sleep(3000);//静默5秒 14 | String androidId = this.getAndroidId(); 15 | System.out.println(androidId); 16 | 17 | String aPackage = this.getWindowSize(); 18 | System.out.println(aPackage); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/options/Mode.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk.options; 2 | 3 | /** 4 | * 用户winbot , 标识前台执行或者后台执行 5 | */ 6 | public enum Mode { 7 | 8 | /** 9 | * 前台执行 10 | */ 11 | front(false), 12 | /** 13 | * 后台执行 14 | */ 15 | backed(true); 16 | 17 | private boolean boolValue; 18 | 19 | Mode(boolean boolValue) { 20 | this.boolValue = boolValue; 21 | } 22 | 23 | public boolean boolValue() { 24 | return boolValue; 25 | } 26 | 27 | public String boolValueStr() { 28 | return Boolean.toString(boolValue); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/server/impl/WinServer.java: -------------------------------------------------------------------------------- 1 | package net.aibote.server.impl; 2 | 3 | import io.netty.channel.ChannelPipeline; 4 | import net.aibote.handler.impl.WinHandler; 5 | import net.aibote.server.BotServer; 6 | 7 | public class WinServer extends BotServer { 8 | 9 | private static final WinServer instance = new WinServer(); 10 | @Override 11 | public int getPort() { 12 | int port = 16999; 13 | return port; 14 | } 15 | 16 | @Override 17 | public void handlers(ChannelPipeline pipeline) { 18 | pipeline.addLast(new WinHandler()); 19 | } 20 | 21 | public static BotServer getInstance() { 22 | return instance; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/server/impl/WebServer.java: -------------------------------------------------------------------------------- 1 | package net.aibote.server.impl; 2 | 3 | import io.netty.channel.ChannelPipeline; 4 | import net.aibote.handler.impl.WebHandler; 5 | import net.aibote.server.BotServer; 6 | 7 | public class WebServer extends BotServer { 8 | 9 | private static final WebServer instance = new WebServer(); 10 | 11 | @Override 12 | public int getPort() { 13 | int port = 16998; 14 | return port; 15 | } 16 | 17 | @Override 18 | public void handlers(ChannelPipeline pipeline) { 19 | pipeline.addLast(new WebHandler()); 20 | } 21 | 22 | public static BotServer getInstance() { 23 | return instance; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/server/impl/AndroidServer.java: -------------------------------------------------------------------------------- 1 | package net.aibote.server.impl; 2 | 3 | import io.netty.channel.ChannelPipeline; 4 | import net.aibote.handler.impl.AndroidHandler; 5 | import net.aibote.server.BotServer; 6 | 7 | public class AndroidServer extends BotServer { 8 | 9 | private static final AndroidServer instance = new AndroidServer(); 10 | @Override 11 | public int getPort() { 12 | int port = 16997; 13 | return port; 14 | } 15 | 16 | @Override 17 | public void handlers(ChannelPipeline pipeline) { 18 | pipeline.addLast(new AndroidHandler()); 19 | } 20 | 21 | public static BotServer getInstance() { 22 | return instance; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/options/GesturePath.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk.options; 2 | 3 | /** 4 | * 手势路径 5 | */ 6 | public class GesturePath { 7 | StringBuilder gesturePathStr = new StringBuilder(); 8 | 9 | public void addXY(int x, int y) { 10 | gesturePathStr.append(x).append("/"); 11 | gesturePathStr.append(y).append("/"); 12 | } 13 | 14 | /** 15 | * 返回原始数据 16 | * 17 | * @return 18 | */ 19 | public String gesturePathStr() { 20 | return gesturePathStr.toString(); 21 | } 22 | 23 | /** 24 | * 返回s 补位信息 25 | * 26 | * @return 27 | */ 28 | public String gesturePathStr(String s) { 29 | gesturePathStr.append(s); 30 | return gesturePathStr.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/App.java: -------------------------------------------------------------------------------- 1 | package net.aibote; 2 | 3 | import net.aibote.scripts.ScriptManager; 4 | import net.aibote.server.impl.WinServer; 5 | 6 | public class App { 7 | public static void main(String[] args) { 8 | /*Thread t1 = new Thread(() -> { 9 | AndroidServer.getInstance().start(); 10 | }); 11 | t1.start(); 12 | 13 | Thread t2 = new Thread(() -> { 14 | WebServer.getInstance().start(); 15 | }); 16 | t2.start();*/ 17 | 18 | Thread t3 = new Thread(() -> { 19 | WinServer.getInstance().start(); 20 | }); 21 | t3.start(); 22 | 23 | Thread scriptManager = Thread.ofVirtual().unstarted(new ScriptManager()); 24 | scriptManager.start(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/AiboteChannel.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import net.aibote.sdk.Aibote; 7 | import net.aibote.utils.ClientType; 8 | 9 | public class AiboteChannel { 10 | 11 | public ChannelHandlerContext aiboteChanel; 12 | 13 | @Getter 14 | @Setter 15 | public ClientType clientType; 16 | 17 | @Getter 18 | private Aibote aibote; 19 | 20 | public AiboteChannel(ChannelHandlerContext aiboteChanel) { 21 | this.aiboteChanel = aiboteChanel; 22 | } 23 | 24 | public void setAibote(Aibote aibote) { 25 | this.aibote = aibote; 26 | this.aibote.aiboteChanel = this.aiboteChanel; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/ClientManager.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.ArrayBlockingQueue; 6 | 7 | public class ClientManager { 8 | private Map clients = new HashMap<>(); 9 | private static final ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(200); 10 | 11 | public synchronized static String poll() { 12 | return arrayBlockingQueue.poll(); 13 | } 14 | 15 | public synchronized static void offer(String keyId) { 16 | arrayBlockingQueue.offer(keyId); 17 | } 18 | 19 | public void add(String keyId, AiboteChannel ctc) { 20 | this.clients.put(keyId, ctc); 21 | ClientManager.offer(keyId); 22 | } 23 | 24 | public void remove(String keyId) { 25 | this.clients.remove(keyId); 26 | } 27 | 28 | public AiboteChannel get(String channelId) { 29 | return clients.get(channelId); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/scripts/ScriptManager.java: -------------------------------------------------------------------------------- 1 | package net.aibote.scripts; 2 | 3 | import net.aibote.handler.AiboteChannel; 4 | import net.aibote.handler.ClientManager; 5 | import net.aibote.handler.impl.AndroidClientManager; 6 | 7 | public class ScriptManager implements Runnable { 8 | 9 | @Override 10 | public void run() { 11 | while (true) { 12 | String channelId = ClientManager.poll(); 13 | if (null != channelId) { 14 | Thread.ofVirtual().start(() -> { 15 | AiboteChannel aiboteChannel = AndroidClientManager.getInstance().get(channelId); 16 | AndroidBotTest androidBotTest = new AndroidBotTest(); 17 | aiboteChannel.setAibote(androidBotTest); 18 | androidBotTest.doScript(); 19 | }); 20 | }else { 21 | try { 22 | Thread.sleep(2000); 23 | } catch (InterruptedException e) { 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sdk-common/src/main/java/net/aibote/utils/Base64Utils.java: -------------------------------------------------------------------------------- 1 | package net.aibote.utils; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | 5 | public class Base64Utils { 6 | 7 | /** 8 | * * BASE64解码 9 | * 10 | * @param base64 11 | * @return 12 | */ 13 | 14 | public static byte[] decodeBASE64(String base64) { 15 | return Base64.decodeBase64(base64.getBytes()); 16 | 17 | } 18 | 19 | 20 | /** 21 | * * BASE64解码 22 | * 23 | * @param base64 24 | * @return 25 | */ 26 | 27 | public static String decodeBASE64AsString(String base64) { 28 | byte[] bytes = decodeBASE64(base64); 29 | return new String(bytes); 30 | 31 | } 32 | 33 | 34 | /** 35 | * * BASE64编码 36 | * 37 | * @param bytes 38 | * @return 39 | */ 40 | 41 | public static String encodeBASE64(byte[] bytes) { 42 | byte[] base64Bytes = Base64.encodeBase64(bytes); 43 | return new String(base64Bytes); 44 | 45 | } 46 | 47 | 48 | /** 49 | * * BASE64编码 50 | * 51 | * @param str 52 | * @return 53 | */ 54 | 55 | public static String encodeBASE64(String str) { 56 | return encodeBASE64(str.getBytes()); 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/handler/BotHandler.java: -------------------------------------------------------------------------------- 1 | package net.aibote.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | 9 | //服务端代码 10 | //服务端处理handler 11 | @Slf4j 12 | public abstract class BotHandler extends SimpleChannelInboundHandler { 13 | private ClientManager clientManager; 14 | 15 | public BotHandler() { 16 | clientManager = getClientManager(); 17 | } 18 | 19 | public abstract ClientManager getClientManager(); 20 | 21 | @Override 22 | public void channelRead0(ChannelHandlerContext ctx, byte[] msg) { 23 | String channelId = ctx.channel().id().asLongText(); 24 | AiboteChannel aiboteChannel = clientManager.get(channelId); 25 | aiboteChannel.getAibote().setRetBuffer(msg); 26 | } 27 | 28 | /** 29 | * 当客户连接服务端之后(打开链接) 获取客户端的channel,并且放到ChannelGroup中去进行管理 30 | */ 31 | @Override 32 | public void handlerAdded(ChannelHandlerContext ctx) { 33 | String channelId = ctx.channel().id().asLongText(); 34 | clientManager.add(channelId, new AiboteChannel(ctx)); 35 | log.info("新的链接: " + channelId); 36 | } 37 | 38 | @Override 39 | public void handlerRemoved(ChannelHandlerContext ctx) { 40 | String channelId = ctx.channel().id().asLongText(); 41 | clientManager.remove(channelId); 42 | log.info("链接断开: " + channelId); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/codec/AiboteEncoder.java: -------------------------------------------------------------------------------- 1 | package net.aibote.codec; 2 | 3 | import io.netty.buffer.ByteBufUtil; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToMessageEncoder; 6 | 7 | import java.nio.CharBuffer; 8 | import java.nio.charset.Charset; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.List; 11 | 12 | 13 | public class AiboteEncoder extends MessageToMessageEncoder { 14 | 15 | private final Charset charset = StandardCharsets.UTF_8; 16 | 17 | /** 18 | * 服务端编码。 协议发送到客户端前,对发送数据进行编码处理 19 | * 20 | * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to 21 | * @param message the message to encode to an other one 22 | * @param out the {@link List} into which the encoded msg should be added 23 | * needs to do some kind of aggregation 24 | */ 25 | @Override 26 | protected void encode(ChannelHandlerContext ctx, String[] message, List out) { 27 | StringBuilder strData = new StringBuilder(); 28 | StringBuilder tempStr = new StringBuilder(); 29 | for (String msg : message) { 30 | tempStr.append(msg); 31 | strData.append(msg.getBytes().length);//获取包含中文实际长度 32 | strData.append('/'); 33 | } 34 | 35 | strData.append('\n'); 36 | strData.append(tempStr); 37 | out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(strData.toString()), this.charset)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/codec/AiboteDecoder.java: -------------------------------------------------------------------------------- 1 | package net.aibote.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.util.List; 9 | 10 | public class AiboteDecoder extends ByteToMessageDecoder { 11 | /** 12 | * 服务端收到客户端发来的协议内容,进行解码 13 | * 只要不out.add , 本次通讯累计的字节都会累加到in中。 14 | * 另外使用getBytes获取,如果是readBytes,则会增量获取数据。 get不会移动指针 15 | * 16 | * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to 17 | * @param in the {@link ByteBuf} from which to read data 18 | * @param out the {@link List} to which decoded messages should be added 19 | */ 20 | @Override 21 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 22 | 23 | int maxLen = in.readableBytes();//获取本次缓冲区总长度 24 | 25 | /* 26 | 先读取7个字节。 为了保护服务器,传输内容最大不能超过9999999个字节包(约10M) 27 | */ 28 | byte[] resultByte = new byte[7]; 29 | in.getBytes(0, resultByte); //get不会移动指针 30 | int size = resultByte.length; 31 | int delimiterIndex = -1; 32 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 33 | for (int i = 0; i < size; i++) { 34 | if (resultByte[i] == 47) //寻找协议头 35 | { 36 | delimiterIndex = i + 1; 37 | break; 38 | } 39 | baos.write(resultByte[i]); 40 | } 41 | int packLen = Integer.valueOf(new String(baos.toByteArray())); //找到包体的长度 42 | if (maxLen >= packLen + delimiterIndex) { 43 | in.skipBytes(delimiterIndex);//跳过包头 44 | resultByte = new byte[packLen];//重新分配读取的缓冲区,传递到handle 45 | in.readBytes(resultByte);//读取数据。并移动in的指针 46 | out.add(resultByte); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sdk-core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | net.aibote 6 | aibote4j 7 | 1.0 8 | 9 | 10 | sdk-core 11 | jar 12 | 13 | sdk-core 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | net.aibote 23 | sdk-common 24 | 25 | 26 | io.netty 27 | netty-all 28 | 29 | 30 | commons-io 31 | commons-io 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-resources-plugin 40 | 3.3.1 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-compiler-plugin 45 | 3.11.0 46 | 47 | ${java.version} 48 | ${java.version} 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sdk-server/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | net.aibote 6 | aibote4j 7 | 1.0 8 | 9 | 10 | sdk-server 11 | jar 12 | 13 | sdk-server 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | net.aibote 23 | sdk-core 24 | 25 | 26 | io.netty 27 | netty-all 28 | 29 | 30 | com.alibaba.fastjson2 31 | fastjson2 32 | 33 | 34 | org.apache.commons 35 | commons-lang3 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-resources-plugin 43 | 3.3.1 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-compiler-plugin 48 | 3.11.0 49 | 50 | ${java.version} 51 | ${java.version} 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /sdk-common/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | net.aibote 6 | aibote4j 7 | 1.0 8 | 9 | 10 | sdk-common 11 | jar 12 | 13 | sdk-common 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.apache.httpcomponents.client5 23 | httpclient5 24 | 25 | 26 | org.apache.httpcomponents.client5 27 | httpclient5-fluent 28 | 29 | 30 | org.apache.commons 31 | commons-lang3 32 | 33 | 34 | commons-codec 35 | commons-codec 36 | 37 | 38 | com.alibaba.fastjson2 39 | fastjson2 40 | 41 | 42 | 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-resources-plugin 47 | 3.3.1 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-compiler-plugin 52 | 3.11.0 53 | 54 | ${java.version} 55 | ${java.version} 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /sdk-common/src/main/java/net/aibote/utils/ImageBase64Converter.java: -------------------------------------------------------------------------------- 1 | package net.aibote.utils; 2 | 3 | 4 | import java.io.*; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | 8 | public class ImageBase64Converter { 9 | /** 10 | * 本地文件(图片、excel等)转换成Base64字符串 11 | * 12 | * @param imgPath 13 | */ 14 | public static String convertFileToBase64(String imgPath) { 15 | byte[] data = null; 16 | // 读取图片字节数组 17 | try { 18 | InputStream in = Files.newInputStream(Paths.get(imgPath)); 19 | System.out.println("文件大小(字节)=" + in.available()); 20 | data = new byte[in.available()]; 21 | in.read(data); 22 | in.close(); 23 | } catch (IOException e) { 24 | e.printStackTrace(); 25 | } 26 | // 对字节数组进行Base64编码,得到Base64编码的字符串 27 | return Base64Utils.encodeBASE64(data); 28 | } 29 | 30 | /** 31 | * 将base64字符串,生成文件 32 | */ 33 | public static File convertBase64ToFile(String fileBase64String, String filePath, String fileName) { 34 | BufferedOutputStream bos = null; 35 | FileOutputStream fos = null; 36 | File file = null; 37 | try { 38 | File dir = new File(filePath); 39 | if (!dir.exists() && dir.isDirectory()) {//判断文件目录是否存在 40 | dir.mkdirs(); 41 | } 42 | 43 | byte[] bfile = Base64Utils.decodeBASE64(fileBase64String); 44 | 45 | file = new File(filePath + File.separator + fileName); 46 | fos = new FileOutputStream(file); 47 | bos = new BufferedOutputStream(fos); 48 | bos.write(bfile); 49 | return file; 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | return null; 53 | } finally { 54 | if (bos != null) { 55 | try { 56 | bos.close(); 57 | } catch (IOException e1) { 58 | e1.printStackTrace(); 59 | } 60 | } 61 | if (fos != null) { 62 | try { 63 | fos.close(); 64 | } catch (IOException e1) { 65 | e1.printStackTrace(); 66 | } 67 | } 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /sdk-server/src/main/java/net/aibote/server/BotServer.java: -------------------------------------------------------------------------------- 1 | package net.aibote.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioChannelOption; 11 | import io.netty.channel.socket.nio.NioServerSocketChannel; 12 | import lombok.extern.slf4j.Slf4j; 13 | import net.aibote.codec.AiboteDecoder; 14 | import net.aibote.codec.AiboteEncoder; 15 | 16 | @Slf4j 17 | public abstract class BotServer { 18 | public abstract int getPort(); 19 | 20 | public abstract void handlers(ChannelPipeline pipeline); 21 | 22 | public void start() { 23 | EventLoopGroup boss = new NioEventLoopGroup(); 24 | EventLoopGroup worker = new NioEventLoopGroup(); 25 | try { 26 | ServerBootstrap bootstrap = new ServerBootstrap(); 27 | bootstrap.group(boss, worker)// 28 | .channel(NioServerSocketChannel.class)// 29 | .childOption(NioChannelOption.SO_KEEPALIVE, true)// 30 | .childOption(NioChannelOption.TCP_NODELAY, true)// 31 | .childHandler(new ChannelInitializer() { 32 | @Override 33 | protected void initChannel(SocketChannel ch) throws Exception { 34 | ChannelPipeline pipeline = ch.pipeline(); 35 | pipeline.addLast("decoder", new AiboteDecoder()); 36 | pipeline.addLast("encoder", new AiboteEncoder()); 37 | //pipeline.addLast("encoder", new StringEncoder()); 38 | handlers(pipeline);//注入自定义处理类 39 | } 40 | }); 41 | log.info("netty server start。。"); 42 | ChannelFuture future = bootstrap.bind(getPort()).sync(); 43 | future.channel().closeFuture().sync(); 44 | } catch (Exception e) { 45 | log.error(e.getMessage(), e); 46 | } finally { 47 | worker.shutdownGracefully(); 48 | boss.shutdownGracefully(); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aibote4j 2 | aibote java版本sdk封装 , 基于jdk21 3 | ### By Reach(QQ:1341191074) Aibote4j交流Q群:496086899 4 | github: https://github.com/1341191074/aibote4j 5 |
6 | 7 | ## 安卓/windows/网页web 三大模拟操作全能型自动化 8 | ``` 9 | 使用方式见 10 | WebBotTest.java 11 | WinBotTest.java 12 | ``` 13 |
14 | 15 | ``` 16 | Aibote是江西爱伯特科技自主研发的一款纯代码RPA办公自动化框架,支持Android、Browser和Windows 三大平台。框架免费、API和接口协议开源,个人、企业商用零费用 17 | 以socket tcp接口协议通信方式命令驱动,支持任何一门计算机语言调用。 18 | 19 | Aibote能力: 20 | 1、AndroidBot,底层自主研发,支持安卓原生APP和H5界面元素和图色定位。元素元素定位速度是Appium框架的的10倍,2340*1080 图色定位仅需要50毫秒! 21 | 2、WindowsBot,底层自主研发,支持Windows应用、.NET、WinForm、WPF、QT、JAVA(Swing和AWT等GUI库)和Electron 等语言开发的窗口界面元素和图色定位,独家xpath算法 简洁急速, 22 | 元素/图色定位速度分别是可视化RPA的3倍和20倍! 23 | 3、WebBot,底层自主研发,支持chromium内核的所有浏览器和应用。C/C++语言基于浏览器内核协议研发而成的一款web自动化框架。速度是selenium 10倍! 24 | 4、Android远程投屏,底层自主研发,可在一台电脑监控观察多台安卓RPA机器人运行状态并批量管理操作 25 | 5、自建OCR服务器,支持文字识别和定位,免费且不限制使用次数! 26 | 6、自研AiboteScriptUI界面开发工具,提供人机交互功能,打包exe发布机器人可以在离线环境运行! 27 | ``` 28 |
29 | 30 | 使用参考 : 31 | ``` JAVA 32 | public class WebBotTest extends WebBot { 33 | 34 | public static void main(String[] args) { 35 | WebBotServer webBotServer = new WebBotServer(); 36 | webBotServer.runLocalClient(19028, null, 0, null, null, null, null); 37 | webBotServer.startServer(WebBotTest.class, 19028); 38 | } 39 | 40 | //模拟远程启动 41 | //WebDriver.exe "{\"serverIp\":\"127.0.0.1\",\"serverPort\":19028,\"browserName\":\"chrome\",\"debugPort\":9223,\"browserPath\":\"null\",\"argument\":\"null\",\"userDataDir\":\"null\",\"extendParam\":\"\"}" 42 | @Override 43 | public void webMain() { 44 | this.sleep(5000); 45 | 46 | boolean ret = this.navigate("https://www.bilibili.com/");//url必须带http:// 47 | log.info(String.valueOf(ret)); 48 | 49 | String curPageId = null; 50 | curPageId = this.getCurPageId(); 51 | log.info("第一次获取pageId : " + curPageId); 52 | 53 | this.sendKeys("//*[@id=\"nav-searchform\"]/div[1]/input", "aibote"); 54 | this.sleep(5000); 55 | 56 | this.clickElement("//*[@id=\"nav-searchform\"]/div[2]"); 57 | this.sleep(5000); 58 | 59 | curPageId = this.getCurPageId(); 60 | log.info("第二次获取pageId : " + curPageId); 61 | 62 | String myconf = (String) super.ymlConfig.get("myconf"); 63 | System.out.println(myconf); 64 | 65 | // this.switchPage(curPageId); 66 | // this.clickElement("//*[@id=\"nav-searchform\"]/div[2]"); 67 | // try { 68 | // Thread.sleep(2000); 69 | // } catch (InterruptedException e) { 70 | // } 71 | 72 | //String base64 = this.takeScreenshot(null); 73 | //log.info(base64); 74 | //this.closeBrowser(); //关闭浏览器时,driver会一同关闭 75 | //this.closeDriver(); 76 | } 77 | } 78 | ``` 79 | 80 | changelog生成 81 | git log --date=format:"%Y-%m-%d" --pretty="- %cd %an %s%n`````` %n%b%n``````" > CHANGELOG.md 82 | 83 | 84 | ```text 85 | 免责声明 86 | 仅供用于学习和交流。 87 | 使用者请勿使用本框架编写商业、违法、等有损他人利益的软件或插件等。使用本框架造成的后果全部由使用者自负,与本人无关。 88 | 本人保留本免责声明的最终解释权。 89 | ``` 90 | 91 | 92 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | net.aibote 6 | aibote4j 7 | 1.0 8 | pom 9 | 10 | aibote4j 11 | http://maven.apache.org 12 | 13 | sdk-core 14 | sdk-common 15 | sdk-server 16 | 17 | 18 | 19 | 21 20 | 21 21 | UTF-8 22 | UTF-8 23 | 21 24 | 4.1.116.Final 25 | 1.18.36 26 | 2.0.16 27 | 1.5.15 28 | 2.0.53 29 | 5.3.1 30 | 3.17.0 31 | 2.18.0 32 | 1.17.1 33 | 2.3 34 | 35 | 36 | 37 | 38 | org.slf4j 39 | slf4j-api 40 | 41 | 42 | ch.qos.logback 43 | logback-classic 44 | 45 | 46 | org.projectlombok 47 | lombok 48 | 49 | 50 | 51 | 52 | 53 | 54 | net.aibote 55 | sdk-common 56 | 1.0 57 | 58 | 59 | net.aibote 60 | sdk-core 61 | 1.0 62 | 63 | 64 | io.netty 65 | netty-all 66 | ${netty-all.version} 67 | 68 | 69 | org.projectlombok 70 | lombok 71 | ${lombok.version} 72 | 73 | 74 | org.slf4j 75 | slf4j-api 76 | ${slf4j-api.version} 77 | 78 | 79 | ch.qos.logback 80 | logback-classic 81 | ${logback-classic.version} 82 | 83 | 84 | com.alibaba.fastjson2 85 | fastjson2 86 | ${fastjson2.version} 87 | 88 | 89 | org.apache.httpcomponents.client5 90 | httpclient5 91 | ${httpcomponents.version} 92 | 93 | 94 | org.apache.httpcomponents.client5 95 | httpclient5-fluent 96 | ${httpcomponents.version} 97 | 98 | 99 | org.apache.commons 100 | commons-lang3 101 | ${commons-lang3.version} 102 | 103 | 104 | commons-io 105 | commons-io 106 | ${commons-io.version} 107 | 108 | 109 | commons-codec 110 | commons-codec 111 | ${commons-codec.version} 112 | 113 | 114 | org.yaml 115 | snakeyaml 116 | ${snakeyaml.version} 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/Aibote.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import lombok.Setter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.commons.lang3.time.StopWatch; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Slf4j 14 | public abstract class Aibote { 15 | private final Object lockObj = new Object();//创建一个 16 | public String runStatus; 17 | 18 | @Setter 19 | private byte[] retBuffer; 20 | 21 | public ChannelHandlerContext aiboteChanel; 22 | 23 | private long retTimeout = 2000; // 正常下获取返回值的时间。 24 | private long retDelayTimeout = 6000; // 超时情况获取返回值的时间。 25 | 26 | public Aibote() { 27 | this.runStatus = "未运行"; 28 | } 29 | 30 | public Aibote(ChannelHandlerContext aiboteChanel) { 31 | this.aiboteChanel = aiboteChanel; 32 | this.runStatus = "未运行"; 33 | } 34 | 35 | public abstract String getScriptName(); 36 | 37 | public abstract void doScript(); 38 | 39 | public static String getVersion() { 40 | return "2023-11-25"; 41 | } 42 | 43 | public void sleep(int millisecondsTimeout) { 44 | try { 45 | Thread.sleep(millisecondsTimeout); 46 | } catch (InterruptedException e) { 47 | } 48 | } 49 | 50 | private void send(String... arrArgs) { 51 | this.send(this.retTimeout, arrArgs); 52 | } 53 | 54 | private void send(long timeOut, String... arrArgs) { 55 | if (this.aiboteChanel == null) { 56 | throw new RuntimeException("链接错误"); 57 | } 58 | this.aiboteChanel.writeAndFlush(arrArgs); 59 | StopWatch stopwatch = new StopWatch(); 60 | stopwatch.start(); 61 | synchronized (lockObj) { 62 | while (this.retBuffer == null) { 63 | log.info(String.valueOf( stopwatch.getTime(TimeUnit.MILLISECONDS))); 64 | if (stopwatch.getTime(TimeUnit.MILLISECONDS) > retTimeout) { 65 | break; 66 | } else { 67 | sleep(200); 68 | } 69 | } 70 | } 71 | stopwatch.stop(); 72 | } 73 | 74 | private void sendBytes(byte[] arrArgs) { 75 | if (this.aiboteChanel == null) { 76 | throw new RuntimeException("链接错误"); 77 | } 78 | this.aiboteChanel.writeAndFlush(arrArgs); 79 | StopWatch stopwatch = new StopWatch(); 80 | stopwatch.start(); 81 | synchronized (lockObj) { 82 | while (this.retBuffer == null) { 83 | if (stopwatch.getTime(TimeUnit.MILLISECONDS) > retTimeout) { 84 | break; 85 | } else { 86 | sleep(200); 87 | } 88 | } 89 | } 90 | stopwatch.stop(); 91 | } 92 | 93 | public byte[] bytesCmd(String... arrArgs) { 94 | this.send(arrArgs); 95 | if (this.retBuffer != null) { 96 | byte[] ret = this.retBuffer; 97 | this.retBuffer = null; 98 | return ret; 99 | } 100 | return null; 101 | } 102 | 103 | public boolean boolCmd(String... arrArgs) { 104 | this.send(arrArgs); 105 | if (this.retBuffer != null) { 106 | byte[] ret = this.retBuffer; 107 | this.retBuffer = null; 108 | return "true".equals(new String(ret)); 109 | } 110 | return false; 111 | } 112 | 113 | public boolean boolDelayCmd(String... arrArgs) { 114 | this.send(this.retDelayTimeout, arrArgs); 115 | if (this.retBuffer != null) { 116 | byte[] ret = this.retBuffer; 117 | this.retBuffer = null; 118 | return "true".equals(new String(ret)); 119 | } 120 | return false; 121 | } 122 | 123 | protected String strCmd(String... arrArgs) { 124 | this.send(arrArgs); 125 | if (this.retBuffer != null) { 126 | byte[] ret = this.retBuffer; 127 | this.retBuffer = null; 128 | String retStr = new String(ret); 129 | if (!"null".equals(retStr)) { 130 | return retStr; 131 | } 132 | } 133 | return null; 134 | } 135 | 136 | protected String strDelayCmd(String... arrArgs) { 137 | this.send(this.retDelayTimeout, arrArgs); 138 | if (this.retBuffer != null) { 139 | byte[] ret = this.retBuffer; 140 | this.retBuffer = null; 141 | String retStr = new String(ret); 142 | if (!"null".equals(retStr)) { 143 | return retStr; 144 | } 145 | } 146 | return null; 147 | } 148 | 149 | protected boolean sendFile(String functionName, String androidFilePath, byte[] fileData) { 150 | StringBuilder strData = new StringBuilder(); 151 | strData.append(functionName.getBytes().length).append("/"); 152 | strData.append(androidFilePath.getBytes().length).append("/"); 153 | strData.append(fileData.length).append("/"); 154 | strData.append(functionName); 155 | strData.append(androidFilePath); 156 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 157 | try { 158 | byteArrayOutputStream.write(strData.toString().getBytes(StandardCharsets.UTF_8)); 159 | byteArrayOutputStream.write(fileData); 160 | this.sendBytes(byteArrayOutputStream.toByteArray()); 161 | if (this.retBuffer != null) { 162 | byte[] ret = this.retBuffer; 163 | this.retBuffer = null; 164 | return "true".equals(new String(ret)); 165 | } 166 | } catch (IOException ex) { 167 | throw new RuntimeException(ex); 168 | } 169 | return false; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /sdk-common/src/main/java/net/aibote/utils/HttpClientUtils.java: -------------------------------------------------------------------------------- 1 | package net.aibote.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.hc.client5.http.classic.methods.*; 6 | import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; 7 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 8 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; 9 | import org.apache.hc.client5.http.impl.classic.HttpClients; 10 | import org.apache.hc.core5.http.HttpEntity; 11 | import org.apache.hc.core5.http.NameValuePair; 12 | import org.apache.hc.core5.http.ParseException; 13 | import org.apache.hc.core5.http.io.entity.EntityUtils; 14 | import org.apache.hc.core5.http.io.entity.StringEntity; 15 | import org.apache.hc.core5.http.message.BasicNameValuePair; 16 | 17 | import java.io.IOException; 18 | import java.io.UnsupportedEncodingException; 19 | import java.nio.charset.Charset; 20 | import java.nio.charset.StandardCharsets; 21 | import java.util.*; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * 工具类 httpclient5 27 | */ 28 | @Slf4j 29 | public class HttpClientUtils { 30 | 31 | private static CloseableHttpClient httpClient; 32 | 33 | /** 34 | * post请求 json参数 35 | * 36 | * @param url 37 | * @param bodyJsonParams 38 | * @param headers 39 | * @return 40 | * @throws IOException 41 | */ 42 | public static String doPost(String url, String bodyJsonParams, Map headers) throws Exception { 43 | HttpPost httpPost = new HttpPost(url); 44 | //httpPost.setProtocolVersion(new ProtocolVersion("HTTP", 1, 0)); 45 | httpPost.addHeader("Content-Type", "application/json"); 46 | httpPost.setEntity(new StringEntity(bodyJsonParams, StandardCharsets.UTF_8)); 47 | 48 | addHeader(httpPost, headers); 49 | return execute(httpPost); 50 | } 51 | 52 | /** 53 | * post请求 json参数 54 | * 55 | * @param url 56 | * @param httpEntity 57 | * @param headers 58 | * @return 59 | * @throws IOException 60 | */ 61 | public static String doPost(String url, HttpEntity httpEntity, Map headers) throws Exception { 62 | HttpPost httpPost = new HttpPost(url); 63 | //httpPost.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1)); 64 | //httpPost.addHeader("Content-Type", "application/json"); 65 | httpPost.setEntity(httpEntity); 66 | 67 | addHeader(httpPost, headers); 68 | return execute(httpPost); 69 | } 70 | 71 | /** 72 | * post k-v参数 73 | * 74 | * @param url 75 | * @param params 76 | * @param headers 77 | * @return 78 | * @throws IOException 79 | */ 80 | public static String doPost(String url, Map params, Map headers) throws Exception { 81 | HttpPost httpPost = new HttpPost(url); 82 | //httpPost.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1)); 83 | if (params != null && !params.keySet().isEmpty()) { 84 | httpPost.setEntity(getUrlEncodedFormEntity(params)); 85 | } 86 | addHeader(httpPost, headers); 87 | return execute(httpPost); 88 | } 89 | 90 | /** 91 | * patch json参数 92 | * 93 | * @param url 94 | * @param bodyJsonParams 95 | * @param headers 96 | * @return 97 | * @throws IOException 98 | */ 99 | public static String doPatch(String url, String bodyJsonParams, Map headers) throws Exception { 100 | HttpPatch httpPatch = new HttpPatch(url); 101 | httpPatch.setEntity(new StringEntity(bodyJsonParams)); 102 | addHeader(httpPatch, headers); 103 | return execute(httpPatch); 104 | } 105 | 106 | /** 107 | * patch k-v参数 108 | * 109 | * @param url 110 | * @param params 111 | * @param headers 112 | * @return 113 | * @throws IOException 114 | */ 115 | public static String doPatch(String url, Map params, Map headers) throws Exception { 116 | HttpPatch httpPatch = new HttpPatch(url); 117 | if (params != null && !params.isEmpty()) { 118 | httpPatch.setEntity(getUrlEncodedFormEntity(params)); 119 | } 120 | addHeader(httpPatch, headers); 121 | return execute(httpPatch); 122 | } 123 | 124 | /** 125 | * PUT JSON参数 126 | * 127 | * @param url 128 | * @param bodyJsonParams 129 | * @param headers 130 | * @return 131 | * @throws IOException 132 | */ 133 | public static String doPut(String url, String bodyJsonParams, Map headers) throws Exception { 134 | HttpPut httpPut = new HttpPut(url); 135 | httpPut.addHeader("Content-Type", "application/json"); 136 | httpPut.setEntity(new StringEntity(bodyJsonParams, StandardCharsets.UTF_8)); 137 | 138 | addHeader(httpPut, headers); 139 | return execute(httpPut); 140 | } 141 | 142 | /** 143 | * put k-v参数 144 | * 145 | * @param url 146 | * @param params 147 | * @param headers 148 | * @return 149 | * @throws IOException 150 | */ 151 | public static String doPut(String url, Map params, Map headers) throws Exception { 152 | HttpPut httpPut = new HttpPut(url); 153 | if (params != null && params.keySet().isEmpty()) { 154 | httpPut.setEntity(getUrlEncodedFormEntity(params)); 155 | } 156 | addHeader(httpPut, headers); 157 | return execute(httpPut); 158 | } 159 | 160 | /** 161 | * delete k-v参数 162 | * 163 | * @param url 164 | * @param params 165 | * @param headers 166 | * @return 167 | * @throws IOException 168 | */ 169 | public static String doDelete(String url, Map params, Map headers) throws Exception { 170 | 171 | StringBuilder paramsBuilder = new StringBuilder(url); 172 | if (params != null && !params.keySet().isEmpty()) { 173 | if (url.indexOf("?") == -1) { 174 | paramsBuilder.append("?"); 175 | } 176 | String paramsStr = EntityUtils.toString(Objects.requireNonNull(getUrlEncodedFormEntity(params))); 177 | paramsBuilder.append(paramsStr); 178 | } 179 | 180 | HttpDelete httpDelete = new HttpDelete(paramsBuilder.toString()); 181 | addHeader(httpDelete, headers); 182 | 183 | return execute(httpDelete); 184 | } 185 | 186 | /** 187 | * head请求 188 | * 189 | * @param url 190 | * @param headers 191 | * @return 192 | * @throws IOException 193 | */ 194 | public static String doHeader(String url, Map headers) throws Exception { 195 | HttpHead httpHead = new HttpHead(url); 196 | addHeader(httpHead, headers); 197 | return execute(httpHead); 198 | 199 | } 200 | 201 | /** 202 | * get请求 203 | * 204 | * @param url 205 | * @param params 206 | * @param headers 207 | * @return 208 | * @throws IOException 209 | */ 210 | public static String doGet(String url, Map params, Map headers) throws Exception { 211 | // 参数 212 | StringBuilder paramsBuilder = new StringBuilder(url); 213 | if (params != null && !params.keySet().isEmpty()) { 214 | if (url.indexOf("?") == -1) { 215 | paramsBuilder.append("?"); 216 | } 217 | String paramsStr = EntityUtils.toString(getUrlEncodedFormEntity(params)); 218 | paramsBuilder.append(paramsStr); 219 | } 220 | HttpGet httpGet = new HttpGet(paramsBuilder.toString()); 221 | addHeader(httpGet, headers); 222 | return execute(httpGet); 223 | } 224 | 225 | /** 226 | * 执行请求并返回string值 227 | * 228 | * @param httpUriRequest 229 | * @return 230 | * @throws IOException 231 | */ 232 | private static String execute(HttpUriRequest httpUriRequest) throws IOException, ParseException { 233 | if (null == httpClient) { 234 | synchronized (httpUriRequest) { 235 | httpClient = HttpClients.createDefault(); 236 | log.info("加锁,创建httpClient"); 237 | } 238 | } 239 | CloseableHttpResponse response = httpClient.execute(httpUriRequest); 240 | String defaultCharset = "UTF-8"; 241 | if (null != response.getEntity().getContentType()) { 242 | String charset = getCharSet(response.getEntity().getContentType()); 243 | if (!StringUtils.isEmpty(charset)) { 244 | defaultCharset = charset; 245 | } 246 | } 247 | 248 | return EntityUtils.toString(response.getEntity(), defaultCharset); 249 | 250 | } 251 | 252 | /** 253 | * 添加请求头部 254 | * 255 | * @param httpUriRequest 256 | * @param headers 257 | */ 258 | private static void addHeader(HttpUriRequest httpUriRequest, Map headers) { 259 | if (httpUriRequest != null) { 260 | if (headers != null && !headers.keySet().isEmpty()) { 261 | Set keySet = headers.keySet(); 262 | for (String key : keySet) { 263 | String value = headers.get(key); 264 | httpUriRequest.addHeader(key, value); 265 | } 266 | } 267 | } 268 | } 269 | 270 | /** 271 | * 获取 UrlEncodedFormEntity 参数实体 272 | * 273 | * @param params 274 | * @return 275 | * @throws UnsupportedEncodingException 276 | */ 277 | private static UrlEncodedFormEntity getUrlEncodedFormEntity(Map params) throws UnsupportedEncodingException { 278 | if (params != null && !params.keySet().isEmpty()) { 279 | List list = new ArrayList<>(); 280 | Set keySet = params.keySet(); 281 | Iterator iterator = keySet.iterator(); 282 | while (iterator.hasNext()) { 283 | String key = iterator.next(); 284 | log.info("key :" + key); 285 | Object value = params.get(key); 286 | log.info("value:" + value); 287 | if (value == null) { 288 | continue; 289 | } 290 | String valueStr = value.toString(); 291 | list.add(new BasicNameValuePair(key, valueStr)); 292 | } 293 | return new UrlEncodedFormEntity(list, Charset.defaultCharset()); 294 | } 295 | return null; 296 | } 297 | 298 | /** 299 | * 根据HTTP 响应头部的content type抓取响应的字符集编码 300 | * 301 | * @param content 302 | * @return 303 | */ 304 | private static String getCharSet(String content) { 305 | String regex = ".*charset=([^;]*).*"; 306 | Pattern pattern = Pattern.compile(regex); 307 | Matcher matcher = pattern.matcher(content); 308 | if (matcher.find()) return matcher.group(1); 309 | else return null; 310 | } 311 | 312 | } 313 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | ======================================================================= 204 | Apache ShardingSphere Subcomponents: 205 | 206 | The Apache ShardingSphere project contains subcomponents with separate copyright 207 | notices and license terms. Your use of the source code for the these 208 | subcomponents is subject to the terms and conditions of the following 209 | licenses. 210 | 211 | ======================================================================== 212 | Apache 2.0 licenses 213 | ======================================================================== 214 | 215 | The following components are provided under the Apache License. See project link for details. 216 | The text of each license is the standard Apache 2.0 license. 217 | 218 | Maven Wrapper(mvnw, mvnw.cmd files in root path), https://github.com/apache/maven-wrapper, Apache 2.0 219 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/WebBot.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.util.Map; 8 | 9 | @Data 10 | @EqualsAndHashCode(callSuper = true) 11 | public abstract class WebBot extends Aibote { 12 | 13 | /** 14 | * 导航至 url 15 | * 16 | * @param url 网址 17 | * @return boolean 18 | */ 19 | public boolean navigate(String url) { 20 | return boolCmd("goto", url); 21 | } 22 | 23 | /** 24 | * 新建tab页面并跳转到指定url 25 | * 26 | * @param url 网址 27 | * @return boolean 28 | */ 29 | public boolean newPage(String url) { 30 | return boolCmd("newPage", url); 31 | } 32 | 33 | /** 34 | * 返回 35 | * 36 | * @return boolean 37 | */ 38 | public boolean back() { 39 | return boolCmd("back"); 40 | } 41 | 42 | /** 43 | * 前进 44 | * 45 | * @return boolean 46 | */ 47 | public boolean forward() { 48 | return boolCmd("forward"); 49 | } 50 | 51 | 52 | /** 53 | * 刷新 54 | * 55 | * @return boolean 56 | */ 57 | public boolean refresh() { 58 | return boolCmd("refresh"); 59 | } 60 | 61 | /** 62 | * 获取当前页面id 63 | * 64 | * @return String 65 | */ 66 | public String getCurPageId() { 67 | return strCmd("getCurPageId"); 68 | } 69 | 70 | /** 71 | * 获取所有页面id 72 | * 73 | * @return String 74 | */ 75 | public String getAllPageId() { 76 | return strCmd("getAllPageId"); 77 | } 78 | 79 | 80 | /** 81 | * 切换指定页面 82 | * 83 | * @return boolean 84 | */ 85 | public boolean switchPage(String pageId) { 86 | return boolCmd("switchPage", pageId); 87 | } 88 | 89 | /** 90 | * 关闭当前页面 91 | * 92 | * @return boolean 93 | */ 94 | public boolean closePage() { 95 | return boolCmd("closePage"); 96 | } 97 | 98 | /** 99 | * 获取当前url 100 | * 101 | * @return String 102 | */ 103 | public String getCurrentUrl() { 104 | return strCmd("getCurrentUrl"); 105 | } 106 | 107 | 108 | /** 109 | * 获取当前标题 110 | * 111 | * @return String 112 | */ 113 | public String getTitle() { 114 | return strCmd("getTitle"); 115 | } 116 | 117 | /** 118 | * 切换frame 119 | * 120 | * @param xpath xpath路径 121 | * @return boolean 122 | */ 123 | public boolean switchFrame(String xpath) { 124 | return boolCmd("switchFrame", xpath); 125 | } 126 | 127 | 128 | /** 129 | * 切换到主frame 130 | * 131 | * @return boolean 132 | */ 133 | public boolean switchMainFrame() { 134 | return boolCmd("switchMainFrame"); 135 | } 136 | 137 | /** 138 | * 点击元素 139 | * 140 | * @param xpath xpath路径 141 | * @return boolean 142 | */ 143 | public boolean clickElement(String xpath) { 144 | return boolCmd("clickElement", xpath); 145 | } 146 | 147 | /** 148 | * 设置编辑框值 149 | * 150 | * @param xpath xpath路径 151 | * @param value 目标值 152 | * @return boolean 153 | */ 154 | public boolean setElementValue(String xpath, String value) { 155 | return boolCmd("setElementValue", xpath, value); 156 | } 157 | 158 | 159 | /** 160 | * 获取文本 161 | * 162 | * @param xpath xpath路径 163 | * @return boolean 164 | */ 165 | public String getElementText(String xpath) { 166 | return strCmd("getElementText", xpath); 167 | } 168 | 169 | 170 | /** 171 | * 获取outerHTML 172 | * 173 | * @param xpath xpath路径 174 | * @return boolean 175 | */ 176 | public String getElementOuterHTML(String xpath) { 177 | return strCmd("getElementOuterHTML", xpath); 178 | } 179 | 180 | 181 | /** 182 | * 获取innerHTML 183 | * 184 | * @param xpath xpath路径 185 | * @return boolean 186 | */ 187 | public String getElementInnerHTML(String xpath) { 188 | return strCmd("getElementInnerHTML", xpath); 189 | } 190 | 191 | /** 192 | * 设置属性值 193 | * 194 | * @param xpath xpath路径 195 | * @param value 属性值 196 | * @return boolean 197 | */ 198 | public boolean setElementAttribute(String xpath, String value) { 199 | return boolCmd("setElementAttribute", xpath, value); 200 | } 201 | 202 | /** 203 | * 获取指定属性的值 204 | * 205 | * @param xpath xpath路径 206 | * @param attribute 属性名 207 | * @return boolean 208 | */ 209 | public String getElementAttribute(String xpath, String attribute) { 210 | return strCmd("getElementAttribute", xpath, attribute); 211 | } 212 | 213 | 214 | /** 215 | * 获取矩形位置 216 | * 217 | * @param xpath xpath路径 218 | * @return boolean 219 | */ 220 | public String getElementRect(String xpath) { 221 | return strCmd("getElementRect", xpath); 222 | } 223 | 224 | 225 | /** 226 | * 判断元素是否选中 227 | * 228 | * @param xpath xpath路径 229 | * @return boolean 230 | */ 231 | public boolean isSelected(String xpath) { 232 | return boolCmd("isSelected", xpath); 233 | } 234 | 235 | 236 | /** 237 | * 判断元素是否可见 238 | * 239 | * @param xpath xpath路径 240 | * @return boolean 241 | */ 242 | public boolean isDisplayed(String xpath) { 243 | return boolCmd("isDisplayed", xpath); 244 | } 245 | 246 | /** 247 | * 判断元素是否可用 248 | * 249 | * @param xpath xpath路径 250 | * @return boolean 251 | */ 252 | public boolean isEnabled(String xpath) { 253 | return boolCmd("isEnabled", xpath); 254 | } 255 | 256 | /** 257 | * 清空元素 258 | * 259 | * @param xpath xpath路径 260 | * @return boolean 261 | */ 262 | public boolean clearElement(String xpath) { 263 | return boolCmd("clearElement", xpath); 264 | } 265 | 266 | /** 267 | * 设置元素焦点 268 | * 269 | * @param xpath xpath路径 270 | * @return boolean 271 | */ 272 | public boolean setElementFocus(String xpath) { 273 | return boolCmd("setElementFocus", xpath); 274 | } 275 | 276 | /** 277 | * 通过元素上传文件 278 | * 279 | * @param xpath xpath路径 280 | * @param uploadFiles 上传的文件路径 281 | * @return boolean 282 | */ 283 | public boolean uploadFile(String xpath, String uploadFiles) { 284 | return boolCmd("uploadFile", xpath, uploadFiles); 285 | } 286 | 287 | /** 288 | * 显示元素xpath路径,页面加载完毕再调用。 289 | * 调用此函数后,可在页面移动鼠标会显示元素区域。移动并按下ctrl键,会在浏览器控制台打印相对xpath 和 绝对xpath路径 290 | * ifrmae 内的元素,需要先调用 switchFrame 切入进去,再调用showXpath函数 291 | * 292 | * @return {Promise.} 总是返回true 293 | */ 294 | public boolean showXpath() { 295 | return boolCmd("showXpath"); 296 | } 297 | 298 | /** 299 | * 输入文本 300 | * 301 | * @param xpath xpath路径 302 | * @param txt 文本内容 303 | * @return boolean 304 | */ 305 | public boolean sendKeys(String xpath, String txt) { 306 | return boolCmd("sendKeys", xpath, txt); 307 | } 308 | 309 | 310 | /** 311 | * 发送Vk虚拟键 312 | * 313 | * @param vk 虚拟键 314 | * @return boolean 315 | */ 316 | public boolean sendVk(String vk) { 317 | return boolCmd("sendVk", vk); 318 | } 319 | 320 | /** 321 | * 单击鼠标 322 | * 323 | * @param x x 横坐标,非Windows坐标,页面左上角为起始坐标 324 | * @param y y 纵坐标,非Windows坐标,页面左上角为起始坐标 325 | * @param opt 功能键。单击左键:1 单击右键:2 按下左键:3 弹起左键:4 按下右键:5 弹起右键:6 双击左键:7 326 | * @return boolean 327 | */ 328 | public boolean clickMouse(String x, String y, String opt) { 329 | return boolCmd("clickMouse", x, y, opt); 330 | } 331 | 332 | /** 333 | * 移动鼠标 334 | * 335 | * @param x x 横坐标,非Windows坐标,页面左上角为起始坐标 336 | * @param y y 纵坐标,非Windows坐标,页面左上角为起始坐标 337 | * @return boolean 338 | */ 339 | public boolean moveMouse(String x, String y) { 340 | return boolCmd("moveMouse", x, y); 341 | } 342 | 343 | /** 344 | * 滚动鼠标 345 | * 346 | * @param deltaX deltaX 水平滚动条移动的距离 347 | * @param deltaY deltaY 垂直滚动条移动的距离 348 | * @param x 可选参数,鼠标横坐标位置, 默认为0 349 | * @param y 可选参数,鼠标纵坐标位置, 默认为0 350 | * @return boolean 351 | */ 352 | public boolean wheelMouse(String deltaX, String deltaY, String x, String y) { 353 | if (StringUtils.isBlank(x)) { 354 | x = "0"; 355 | } 356 | if (StringUtils.isBlank(y)) { 357 | y = "0"; 358 | } 359 | return boolCmd("wheelMouse", deltaX, deltaY, x, y); 360 | } 361 | 362 | /** 363 | * 通过xpath 点击鼠标 364 | * 365 | * @param xpath xpath路径 366 | * @param opt 功能键。单击左键:1 单击右键:2 按下左键:3 弹起左键:4 按下右键:5 弹起右键:6 双击左键:7 367 | * @return 368 | */ 369 | public boolean clickMouseByXpath(String xpath, String opt) { 370 | return boolCmd("clickMouseByXpath", xpath, opt); 371 | } 372 | 373 | /** 374 | * xpath移动鼠标(元素中心点) 375 | * 376 | * @param xpath xpath路径 377 | * @return boolean 378 | */ 379 | public boolean moveMouseByXpath(String xpath) { 380 | return boolCmd("moveMouseByXpath", xpath); 381 | } 382 | 383 | /** 384 | * xpath滚动鼠标 385 | * 386 | * @param xpath 元素路径 387 | * @param deltaX 水平滚动条移动的距离 388 | * @param deltaY 垂直滚动条移动的距离 389 | * @return boolean 390 | */ 391 | public boolean wheelMouseByXpath(String xpath, String deltaX, String deltaY) { 392 | return boolCmd("wheelMouseByXpath", xpath, deltaX, deltaY); 393 | } 394 | 395 | 396 | /** 397 | * 截图 398 | * 399 | * @param xpath 可选参数,元素路径。如果指定该参数则截取元素图片 400 | * @return String 401 | */ 402 | public String takeScreenshot(String xpath) { 403 | if (StringUtils.isBlank(xpath)) { 404 | return strCmd("takeScreenshot"); 405 | } 406 | return strCmd("takeScreenshot", xpath); 407 | } 408 | 409 | 410 | /** 411 | * 点击警告框 412 | * 413 | * @param acceptOrCancel true接受, false取消 414 | * @param promptText 可选参数,输入prompt警告框文本 415 | * @return boolean 416 | */ 417 | public boolean clickAlert(boolean acceptOrCancel, String promptText) { 418 | return boolCmd("clickAlert", Boolean.toString(acceptOrCancel), promptText); 419 | } 420 | 421 | 422 | /** 423 | * 截图 424 | * 425 | * @return String 426 | */ 427 | public String getAlertText() { 428 | return strCmd("getAlertText"); 429 | } 430 | 431 | /** 432 | * 获取指定url匹配的cookies 433 | * 434 | * @param url 指定的url http://或https:// 起头 435 | * @return 成功返回json格式的字符串,失败返回null 436 | */ 437 | public String getCookies(String url) { 438 | return strCmd("getCookies", url); 439 | } 440 | 441 | /** 442 | * 获取指定url匹配的cookies 443 | * 444 | * @return 成功返回json格式的字符串,失败返回null 445 | */ 446 | public String getAllCookies() { 447 | return strCmd("getAllCookies"); 448 | } 449 | 450 | public boolean setCookie(String name, String value, String url) { 451 | String domain = "", path = "", sameSite = "", priority = "", sourceScheme = "", partitionKey = ""; 452 | boolean secure = false, httpOnly = false, sameParty = false; 453 | int expires = 0, sourcePort = 0; 454 | return setCookie(name, value, url, domain, path, secure, httpOnly, sameSite, expires, priority, sameParty, sourceScheme, sourcePort, partitionKey); 455 | } 456 | 457 | public boolean setCookie(String name, String value, String url, String domain) { 458 | String path = "", sameSite = "", priority = "", sourceScheme = "", partitionKey = ""; 459 | boolean secure = false, httpOnly = false, sameParty = false; 460 | int expires = 0, sourcePort = 0; 461 | return setCookie(name, value, url, domain, path, secure, httpOnly, sameSite, expires, priority, sameParty, sourceScheme, sourcePort, partitionKey); 462 | } 463 | 464 | public boolean setCookie(String name, String value, String url, String domain, Map options) { 465 | String path = "", sameSite = "", priority = "", sourceScheme = "", partitionKey = ""; 466 | boolean secure = false, httpOnly = false, sameParty = false; 467 | int expires = 0, sourcePort = 0; 468 | if (StringUtils.isBlank(options.get("path"))) { 469 | path = options.get("path"); 470 | } 471 | if (StringUtils.isBlank(options.get("sameSite"))) { 472 | sameSite = options.get("sameSite"); 473 | } 474 | if (StringUtils.isBlank(options.get("priority"))) { 475 | priority = options.get("priority"); 476 | } 477 | if (StringUtils.isBlank(options.get("sourceScheme"))) { 478 | sourceScheme = options.get("sourceScheme"); 479 | } 480 | if (StringUtils.isBlank(options.get("partitionKey"))) { 481 | partitionKey = options.get("partitionKey"); 482 | } 483 | if (StringUtils.isBlank(options.get("secure"))) { 484 | if ("true".equals(options.get("secure"))) { 485 | secure = true; 486 | } 487 | } 488 | if (StringUtils.isBlank(options.get("httpOnly"))) { 489 | if ("true".equals(options.get("httpOnly"))) { 490 | httpOnly = true; 491 | } 492 | } 493 | if (StringUtils.isBlank(options.get("sameParty"))) { 494 | if ("sameParty".equals(options.get("sameParty"))) { 495 | sameParty = true; 496 | } 497 | } 498 | if (StringUtils.isNumeric(options.get("expires"))) { 499 | expires = Integer.parseInt(options.get("expires")); 500 | } 501 | if (StringUtils.isNumeric(options.get("sourcePort"))) { 502 | sourcePort = Integer.parseInt(options.get("sourcePort")); 503 | } 504 | return setCookie(name, value, url, domain, path, secure, httpOnly, sameSite, expires, priority, sameParty, sourceScheme, sourcePort, partitionKey); 505 | } 506 | 507 | /** 508 | * 设置cookie name、value和url必填参数,其他参数可选 509 | * 510 | * @param name String 511 | * @param value String 512 | * @param url String 513 | * @param domain String 514 | * @param path String 515 | * @param secure boolean 516 | * @param httpOnly boolean 517 | * @param sameSite String 518 | * @param expires String 519 | * @param priority String 520 | * @param sameParty boolean 521 | * @param sourceScheme String 522 | * @param sourcePort String 523 | * @param partitionKey String 524 | * @return 525 | */ 526 | public boolean setCookie(String name, String value, String url, String domain, String path, boolean secure, boolean httpOnly, String sameSite, int expires, String priority, boolean sameParty, String sourceScheme, int sourcePort, String partitionKey) { 527 | return boolCmd("setCookie", name, value, url, domain, path, Boolean.toString(secure), Boolean.toString(httpOnly), sameSite, Integer.toString(expires), priority, Boolean.toString(sameParty), sourceScheme, Integer.toString(sourcePort), partitionKey); 528 | } 529 | 530 | /** 531 | * 删除指定cookies 532 | * 533 | * @param name 要删除的 Cookie 的名称。 534 | * @return boolean 535 | */ 536 | public boolean deleteCookies(String name) { 537 | return boolCmd("deleteCookies", name); 538 | } 539 | 540 | /** 541 | * 删除指定cookies 542 | * 543 | * @param name 要删除的 Cookie 的名称。 544 | * @param url url 545 | * @return boolean 546 | */ 547 | public boolean deleteCookies(String name, String url) { 548 | return boolCmd("deleteCookies", name, url); 549 | } 550 | 551 | /** 552 | * 删除指定cookies 553 | * 554 | * @param name 要删除的 Cookie 的名称。 555 | * @param url url 556 | * @return boolean 557 | */ 558 | public boolean deleteCookies(String name, String url, String domain) { 559 | return boolCmd("deleteCookies", name, url, domain); 560 | } 561 | 562 | /** 563 | * 删除指定cookies 564 | * 565 | * @param name 要删除的 Cookie 的名称。 566 | * @param url url 567 | * @return boolean 568 | */ 569 | public boolean deleteCookies(String name, String url, String domain, String path) { 570 | return boolCmd("deleteCookies", name, url, domain, path); 571 | } 572 | 573 | /** 574 | * 删除所有cookies 575 | * 576 | * @return boolean 577 | */ 578 | public boolean deleteAllCookies() { 579 | return boolCmd("deleteAllCookies"); 580 | } 581 | 582 | /** 583 | * 注入JavaScript
584 | * 假如注入代码为函数且有return语句,则返回retrun 的值,否则返回null; 注入示例:(function () {return "aibote rpa"})(); 585 | * 586 | * @param command 注入的js代码 587 | * @return 588 | */ 589 | public String executeScript(String command) { 590 | return strCmd("executeScript", command); 591 | } 592 | 593 | /** 594 | * 获取窗口位置和状态
595 | * 成功返回矩形位置和窗口状态,失败返回null 596 | * 597 | * @return {left:number, top:number, width:number, height:number, windowState:string} 598 | */ 599 | public String getWindowPos() { 600 | return strCmd("getWindowPos"); 601 | } 602 | 603 | /** 604 | * 设置窗口位置和状态 605 | * 606 | * @param windowState 窗口状态,正常:"normal" 最小化:"minimized" 最大化:"maximized" 全屏:"fullscreen" 607 | * @param left 可选参数,浏览器窗口位置,此参数仅windowState 值为 "normal" 时有效 608 | * @param top 可选参数,浏览器窗口位置,此参数仅windowState 值为 "normal" 时有效 609 | * @param width 可选参数,浏览器窗口位置,此参数仅windowState 值为 "normal" 时有效 610 | * @param height 可选参数,浏览器窗口位置,此参数仅windowState 值为 "normal" 时有效 611 | * @return 612 | */ 613 | public boolean setWindowPos(String windowState, float left, float top, int width, float height) { 614 | return boolCmd("setWindowPos", Float.toString(left), Float.toString(top), Float.toString(width), Float.toString(height)); 615 | } 616 | 617 | /** 618 | * 获取WebDriver.exe 命令扩展参数,一般用作脚本远程部署场景,WebDriver.exe驱动程序传递参数给脚本服务端 619 | * 620 | * @return String 返回WebDriver 驱动程序的命令行["extendParam"] 字段的参数 621 | */ 622 | public String getExtendParam() { 623 | return strCmd("getExtendParam"); 624 | } 625 | 626 | /** 627 | * 手机浏览器仿真 628 | * 629 | * @param width 宽度 630 | * @param height 高度 631 | * @param userAgent 用户代理 632 | * @param platform 系统,例如 "Android"、"IOS"、"iPhone" 633 | * @param platformVersion 系统版本号,例如 "9.0",应当与userAgent提供的版本号对应 634 | * @param acceptLanguage 可选参数 - 语言,例如 "zh-CN"、"en" 635 | * @param timezoneId 可选参数 - 时区,时区标识,例如"Asia/Shanghai"、"Europe/Berlin"、"Europe/London" 时区应当与 语言、经纬度 对应 636 | * @param latitude 可选参数 - 纬度,例如 31.230416 637 | * @param longitude 可选参数 - 经度,例如 121.473701 638 | * @param accuracy 可选参数 - 准确度,例如 1111 639 | * @return boolean 640 | */ 641 | public boolean mobileEmulation(int width, int height, String userAgent, String platform, String platformVersion, String acceptLanguage, String timezoneId, float latitude, float longitude, float accuracy) { 642 | return boolCmd("mobileEmulation", Integer.toString(width), Integer.toString(height), userAgent, platform, platformVersion, acceptLanguage, timezoneId, Float.toString(latitude), Float.toString(longitude), Float.toString(accuracy)); 643 | } 644 | 645 | /** 646 | * 设置浏览器下载目录 647 | * 648 | * @param downloadDir 存放下载的目录 649 | * @return 成功返回true,失败返回false 650 | */ 651 | public boolean setDownloadDir(String downloadDir) { 652 | return boolCmd("setDownloadDir", downloadDir); 653 | } 654 | 655 | /** 656 | * 关闭浏览器 657 | * 658 | * @return boolean 659 | */ 660 | public boolean closeBrowser() { 661 | return boolCmd("closeBrowser"); 662 | } 663 | 664 | /** 665 | * 关闭WebDriver.exe驱动程序 666 | * 667 | * @return boolean 668 | */ 669 | public boolean closeDriver() { 670 | return boolCmd("closeDriver"); 671 | } 672 | 673 | /** 674 | * 仿真模式 开始触屏 675 | * 676 | * @param x x坐标 677 | * @param y y坐标 678 | * @return 679 | */ 680 | public boolean touchStart(int x, int y) { 681 | return boolCmd("touchStart", Integer.toString(x), Integer.toString(y)); 682 | } 683 | 684 | /** 685 | * 仿真模式 移动触屏 686 | * 687 | * @param x x坐标 688 | * @param y y坐标 689 | * @return 690 | */ 691 | public boolean touchMove(int x, int y) { 692 | return boolCmd("touchMove", Integer.toString(x), Integer.toString(y)); 693 | } 694 | 695 | /** 696 | * 仿真模式 结束触屏 697 | * 698 | * @param x x坐标 699 | * @param y y坐标 700 | * @return 701 | */ 702 | public boolean touchEnd(int x, int y) { 703 | return boolCmd("touchEnd", Integer.toString(x), Integer.toString(y)); 704 | } 705 | 706 | /** 707 | * 激活框架 708 | * 709 | * @param {string} activateKey, 激活密钥,联系管理员 710 | * @return {Promise.} 返回激活信息 711 | */ 712 | public boolean activateFrame(String activateKey) { 713 | return boolCmd("'activateFrame'", activateKey); 714 | } 715 | 716 | } 717 | 718 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/AndroidBot.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import com.alibaba.fastjson2.JSONObject; 5 | import net.aibote.sdk.dto.Point; 6 | import net.aibote.sdk.options.Region; 7 | import org.apache.commons.io.FileUtils; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | public abstract class AndroidBot extends Aibote { 17 | /** 18 | * 截图保存
19 | * 截图保存在客户端本地了 20 | * 21 | * @param savePath 保存的位置 22 | * @param region 截图区域 [10, 20, 100, 200],region默认全屏 23 | * @param thresholdType 算法类型: 24 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0 25 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva 26 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0 27 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变 28 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变 29 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值 30 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 31 | * thresh阈值,maxval最大值,threshold默认保存原图。thresh和maxval同为255时灰度处理 32 | * @return {Promise.} 33 | */ 34 | public boolean saveScreenshot(String savePath, Region region, int thresholdType, int thresh, int maxval) { 35 | if (thresholdType == 5 || thresholdType == 6) { 36 | thresh = 127; 37 | maxval = 255; 38 | } 39 | 40 | return this.boolCmd("saveScreenshot", savePath, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval)); 41 | } 42 | 43 | /** 44 | * 截图
45 | * 截图数据以bytes方式返回服务端 46 | * 47 | * @param region 截图区域 [10, 20, 100, 200],region默认全屏 48 | * @param thresholdType 算法类型: 49 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0 50 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva 51 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0 52 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变 53 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变 54 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值 55 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 56 | * thresh阈值,maxval最大值,threshold默认保存原图。thresh和maxval同为255时灰度处理 57 | * @param scale 保存的位置 58 | * @return {Promise.} 59 | */ 60 | public byte[] takeScreenshot(Region region, int thresholdType, int thresh, int maxval, float scale) { 61 | if (thresholdType == 5 || thresholdType == 6) { 62 | thresh = 127; 63 | maxval = 255; 64 | } 65 | 66 | return this.bytesCmd("takeScreenshot", Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval), Float.toString(scale)); 67 | } 68 | 69 | /** 70 | * 获取指定坐标点的色值 71 | * 72 | * @param x 横坐标 73 | * @param y 纵坐标 74 | * @return {Promise.} 成功返回#开头的颜色值,失败返回null 75 | */ 76 | public String getColor(int x, int y) { 77 | return this.strCmd("getColor", Integer.toString(x), Integer.toString(y)); 78 | } 79 | 80 | /** 81 | * 找图 82 | * 83 | * @param imagePath 小图片路径,多张小图查找应当用"|"分开小图路径 84 | * @param region 区域 85 | * @param sim 图片相似度 0.0-1.0,sim默认0.95 86 | * @param thresholdType thresholdType算法类型:
87 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0 88 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva 89 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0 90 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变 91 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变 92 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值 93 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 94 | * @param thresh 阈值。threshold默认保存原图。thresh和maxval同为255时灰度处理 95 | * @param maxval 最大值。threshold默认保存原图。thresh和maxval同为255时灰度处理 96 | * @param multi 找图数量,默认为1 找单个图片坐标 97 | * @return 成功返回 单坐标点[{x:number, y:number}],多坐标点[{x1:number, y1:number}, {x2:number, y2:number}...] 失败返回null 98 | */ 99 | public String findImages(String imagePath, Region region, float sim, int thresholdType, int thresh, int maxval, int multi) { 100 | if (thresholdType == 5 || thresholdType == 6) { 101 | thresh = 127; 102 | maxval = 255; 103 | } 104 | 105 | return this.strDelayCmd("findImage", imagePath, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval), Integer.toString(multi)); 106 | } 107 | 108 | /** 109 | * 找动态图 110 | * 111 | * @param frameRate 前后两张图相隔的时间,单位毫秒 112 | * @param frameRate 前后两张图相隔的时间,单位毫秒 113 | * @return 成功返回 单坐标点[{x:number, y:number}],多坐标点[{x1:number, y1:number}, {x2:number, y2:number}...] 失败返回null 114 | */ 115 | public String findAnimation(int frameRate, Region region) { 116 | return strDelayCmd("findAnimation", Integer.toString(frameRate), Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom)); 117 | } 118 | 119 | 120 | /** 121 | * 查找指定色值的坐标点 122 | * 123 | * @param strMainColor 颜色字符串,必须以 # 开头,例如:#008577; 124 | * @param subColors 辅助定位的其他颜色; 125 | * @param region 在指定区域内找色,默认全屏; 126 | * @param sim 相似度。0.0-1.0,sim默认为1 127 | * @return String 成功返回 x|y 失败返回null 128 | */ 129 | public String findColor(String strMainColor, net.aibote.sdk.options.SubColor[] subColors, Region region, float sim) { 130 | StringBuilder subColorsStr = new StringBuilder(); 131 | if (null != subColors) { 132 | net.aibote.sdk.options.SubColor subColor; 133 | for (int i = 0; i < subColors.length; i++) { 134 | subColor = subColors[i]; 135 | subColorsStr.append(subColor.offsetX).append("/"); 136 | subColorsStr.append(subColor.offsetY).append("/"); 137 | subColorsStr.append(subColor.colorStr); 138 | if (i < subColors.length - 1) { //最后不需要\n 139 | subColorsStr.append("\n"); 140 | } 141 | } 142 | } 143 | 144 | return this.strDelayCmd("findColor", strMainColor, subColorsStr.toString(), Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim)); 145 | } 146 | 147 | /** 148 | * 比较指定坐标点的颜色值 149 | * 150 | * @param mainX 主颜色所在的X坐标 151 | * @param mainY 主颜色所在的Y坐标 152 | * @param mainColorStr 颜色字符串,必须以 # 开头,例如:#008577; 153 | * @param subColors 辅助定位的其他颜色; 154 | * @param region 截图区域 默认全屏 155 | * @param sim 相似度,0-1 的浮点数 156 | * @return boolean 157 | */ 158 | public boolean compareColor(int mainX, int mainY, String mainColorStr, net.aibote.sdk.options.SubColor[] subColors, Region region, float sim) { 159 | StringBuilder subColorsStr = new StringBuilder(); 160 | if (null != subColors) { 161 | net.aibote.sdk.options.SubColor subColor; 162 | for (int i = 0; i < subColors.length; i++) { 163 | subColor = subColors[i]; 164 | subColorsStr.append(subColor.offsetX).append("/"); 165 | subColorsStr.append(subColor.offsetY).append("/"); 166 | subColorsStr.append(subColor.colorStr); 167 | if (i < subColors.length - 1) { //最后不需要\n 168 | subColorsStr.append("\n"); 169 | } 170 | } 171 | } 172 | return this.boolDelayCmd("compareColor", Integer.toString(mainX), Integer.toString(mainY), mainColorStr, subColorsStr.toString(), Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim)); 173 | } 174 | 175 | /** 176 | * 手指按下 177 | * 178 | * @param x 横坐标 179 | * @param y 纵坐标 180 | * @param duration 按下时长,单位毫秒 181 | * @return {Promise.} 成功返回true 失败返回false 182 | */ 183 | public boolean press(int x, int y, int duration) { 184 | return this.boolCmd("press", Integer.toString(x), Integer.toString(y), Integer.toString(duration)); 185 | } 186 | 187 | 188 | /** 189 | * 手指移动 190 | * 191 | * @param x 横坐标 192 | * @param y 纵坐标 193 | * @param duration 移动时长,单位毫秒 194 | * @return {Promise.} 成功返回true 失败返回false 195 | */ 196 | public boolean move(int x, int y, int duration) { 197 | return this.boolCmd("move", Integer.toString(x), Integer.toString(y), Integer.toString(duration)); 198 | } 199 | 200 | /** 201 | * 手指释放 202 | * 203 | * @return {Promise.} 成功返回true 失败返回false 204 | */ 205 | public boolean release() { 206 | return this.boolCmd("release"); 207 | } 208 | 209 | 210 | /** 211 | * 点击坐标 212 | * 213 | * @param x 横坐标 214 | * @param y 纵坐标 215 | * @return {Promise.} 成功返回true 失败返回false 216 | */ 217 | public boolean click(int x, int y) { 218 | return this.boolCmd("click", Integer.toString(x), Integer.toString(y)); 219 | } 220 | 221 | /** 222 | * 双击坐标 223 | * 224 | * @param x 横坐标 225 | * @param y 纵坐标 226 | * @return {Promise.} 成功返回true 失败返回false 227 | */ 228 | public boolean doubleClick(int x, int y) { 229 | return this.boolCmd("doubleClick", Integer.toString(x), Integer.toString(y)); 230 | } 231 | 232 | /** 233 | * 长按坐标 234 | * 235 | * @param x 横坐标 236 | * @param y 纵坐标 237 | * @param duration 长按时长,单位毫秒 238 | * @return {Promise.} 成功返回true 失败返回false 239 | */ 240 | public boolean longClick(int x, int y, int duration) { 241 | return this.boolCmd("longClick", Integer.toString(x), Integer.toString(y), Integer.toString(duration)); 242 | } 243 | 244 | /** 245 | * 滑动坐标 246 | * 247 | * @param startX 起始横坐标 248 | * @param startY 起始纵坐标 249 | * @param endX 结束横坐标 250 | * @param endY 结束纵坐标 251 | * @param duration 滑动时长,单位毫秒 252 | * @return {Promise.} 成功返回true 失败返回false 253 | */ 254 | public boolean swipe(int startX, int startY, int endX, int endY, float duration) { 255 | return this.boolCmd("swipe", Integer.toString(startX), Integer.toString(startY), Integer.toString(endX), Integer.toString(endY), Float.toString(duration)); 256 | } 257 | 258 | 259 | /** 260 | * 执行手势 261 | * 262 | * @param gesturePath 手势路径 263 | * @param duration 手势时长,单位毫秒 264 | * @return {Promise.} 成功返回true 失败返回false 265 | */ 266 | public boolean dispatchGesture(net.aibote.sdk.options.GesturePath gesturePath, float duration) { 267 | return this.boolCmd("dispatchGesture", gesturePath.gesturePathStr("\n"), Float.toString(duration)); 268 | } 269 | 270 | 271 | /** 272 | * 发送文本 273 | * 274 | * @param text 发送的文本,需要打开aibote输入法 275 | * @return {Promise.} 成功返回true 失败返回false 276 | */ 277 | public boolean sendKeys(String text) { 278 | return this.boolCmd("sendKeys", text); 279 | } 280 | 281 | /** 282 | * 发送按键 283 | * 284 | * @param keyCode 发送的虚拟按键,需要打开aibote输入法。例如:最近应用列表:187 回车:66 285 | * 按键对照表 https://blog.csdn.net/yaoyaozaiye/article/details/122826340 286 | * @return {Promise.} 成功返回true 失败返回false 287 | */ 288 | public boolean sendVk(int keyCode) { 289 | return this.boolCmd("sendVk", Integer.toString(keyCode)); 290 | } 291 | 292 | /** 293 | * 返回 294 | * 295 | * @return {Promise.} 成功返回true 失败返回false 296 | */ 297 | public boolean back() { 298 | return this.boolCmd("back"); 299 | } 300 | 301 | /** 302 | * home 303 | * 304 | * @return {Promise.} 成功返回true 失败返回false 305 | */ 306 | public boolean home() { 307 | return this.boolCmd("home"); 308 | } 309 | 310 | /** 311 | * 显示最近任务 312 | * 313 | * @return {Promise.} 成功返回true 失败返回false 314 | */ 315 | public boolean recents() { 316 | return this.boolCmd("recents"); 317 | } 318 | 319 | /** 320 | * 打开 开/关机 对话框,基于无障碍权限 321 | * 322 | * @return {Promise.} 成功返回true 失败返回false 323 | */ 324 | public boolean powerDialog() { 325 | return this.boolCmd("powerDialog"); 326 | } 327 | 328 | /** 329 | * 初始化ocr服务 330 | * 331 | * @param ocrServerIp ocr服务器IP。当参数值为 "127.0.0.1"时,则使用手机内置的ocr识别,不必打开AiboteAndroidOcr.exe服务端 332 | * @param ocrServerPort ocr服务器端口,默认9527 333 | * @param useAngleModel 支持图像旋转。 默认false 334 | * @param enableGPU 启动GPU 模式。默认false 335 | * @param enableTensorrt 启动加速,仅enableGPU = true 时有效,默认false 336 | * @return {Promise.} 总是返回true 337 | */ 338 | public boolean initOcr(String ocrServerIp, int ocrServerPort, boolean useAngleModel, boolean enableGPU, boolean enableTensorrt) { 339 | if (ocrServerPort <= 0) { 340 | ocrServerPort = 9527; 341 | } 342 | return this.boolCmd("initOcr", ocrServerIp, Integer.toString(ocrServerPort), Boolean.toString(useAngleModel), Boolean.toString(enableGPU), Boolean.toString(enableTensorrt)); 343 | } 344 | 345 | /** 346 | * ocr识别 347 | * 348 | * @param region 区域 349 | * @param thresholdType 二值化算法类型 350 | * @param thresh 阈值 351 | * @param maxval 最大值 352 | * @param scale scale 图片缩放率, 默认为 1.0 353 | * @return String jsonstr 354 | */ 355 | public List ocr(Region region, int thresholdType, int thresh, int maxval, float scale) { 356 | if (null == region) { 357 | region = new Region(); 358 | } 359 | if (thresholdType == 5 || thresholdType == 6) { 360 | thresh = 127; 361 | maxval = 255; 362 | } 363 | String strRet = this.strCmd("ocr", Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval), Float.toString(scale)); 364 | if (null == strRet || strRet == "" || strRet == "null" || strRet == "[]") { 365 | return null; 366 | } else { 367 | List list = new ArrayList<>(); 368 | JSONArray jsonArray = JSONArray.parseArray(strRet); 369 | jsonArray.forEach((ary) -> { 370 | if (ary instanceof JSONArray) { 371 | JSONArray a = (JSONArray) ary; 372 | net.aibote.sdk.dto.OCRResult ocrResult = new net.aibote.sdk.dto.OCRResult(); 373 | ocrResult.lt = new Point(a.getJSONArray(0).getJSONArray(0).getIntValue(0), a.getJSONArray(0).getJSONArray(0).getIntValue(1)); 374 | ocrResult.rt = new Point(a.getJSONArray(0).getJSONArray(1).getIntValue(0), a.getJSONArray(0).getJSONArray(1).getIntValue(1)); 375 | ocrResult.ld = new Point(a.getJSONArray(0).getJSONArray(2).getIntValue(0), a.getJSONArray(0).getJSONArray(2).getIntValue(1)); 376 | ocrResult.rd = new Point(a.getJSONArray(0).getJSONArray(3).getIntValue(0), a.getJSONArray(0).getJSONArray(3).getIntValue(1)); 377 | ocrResult.word = a.getJSONArray(1).getString(0); 378 | ocrResult.rate = a.getJSONArray(1).getFloatValue(1); 379 | 380 | list.add(ocrResult); 381 | } 382 | }); 383 | return list; 384 | } 385 | } 386 | 387 | /** 388 | * 获取屏幕文字 389 | * 390 | * @param region 区域 391 | * @param thresholdType thresholdType算法类型:
392 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0
393 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva
394 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0
395 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变
396 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变
397 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值
398 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 399 | * @param thresh 阈值 400 | * @param maxval 最大值 401 | * @param scale 浮点型 图片缩放率, 默认为 1.0 原大小。大于1.0放大,小于1.0缩小,不能为负数 402 | * @return 失败返回null,成功返窗口上的文字 403 | */ 404 | public String getWords(Region region, int thresholdType, int thresh, int maxval, float scale) { 405 | if (thresholdType == 5 || thresholdType == 6) { 406 | thresh = 127; 407 | maxval = 255; 408 | } 409 | 410 | List wordsResult = null; 411 | wordsResult = this.ocr(region, thresholdType, thresh, maxval, scale); 412 | 413 | if (null == wordsResult) { 414 | return null; 415 | } 416 | 417 | StringBuilder words = new StringBuilder(); 418 | wordsResult.forEach((obj) -> { 419 | words.append(obj.word).append("\n"); 420 | }); 421 | 422 | return words.toString(); 423 | } 424 | 425 | /** 426 | * 查找文字 427 | * 428 | * @param word 要查找的文字 429 | * @param region 区域 430 | * @param thresholdType 算法类型:
431 | * * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0
432 | * * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva
433 | * * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0
434 | * * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变
435 | * * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变
436 | * * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值
437 | * * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 438 | * @param thresh 阈值 439 | * @param maxval 最大值 440 | * @param scale 浮点型 图片缩放率, 默认为 1.0 原大小。大于1.0放大,小于1.0缩小,不能为负数 441 | * @return Point 442 | */ 443 | public Point findWords(String word, Region region, int thresholdType, int thresh, int maxval, float scale) { 444 | if (thresholdType == 5 || thresholdType == 6) { 445 | thresh = 127; 446 | maxval = 255; 447 | } 448 | 449 | List wordsResult = null; 450 | wordsResult = this.ocr(region, thresholdType, thresh, maxval, scale); 451 | 452 | 453 | if (null == wordsResult) { 454 | return null; 455 | } 456 | 457 | Point point = new Point(-1, -1); 458 | StringBuilder words = new StringBuilder(); 459 | Optional first = wordsResult.stream().filter((y) -> y.word.indexOf(word) != -1).findFirst(); 460 | if (first.isPresent()) { 461 | net.aibote.sdk.dto.OCRResult ocrResult = first.get(); 462 | int localLeft, localTop, localRight, localBottom, width, height, wordWidth, offsetX, offsetY, index, x, y; 463 | localLeft = ocrResult.lt.x; 464 | localTop = ocrResult.lt.y; 465 | localRight = ocrResult.ld.x; 466 | localBottom = ocrResult.ld.y; 467 | width = localRight - localLeft; 468 | height = localBottom - localTop; 469 | wordWidth = width / ocrResult.word.length(); 470 | index = ocrResult.word.indexOf(word); 471 | offsetX = wordWidth * (index + words.length() / 2); 472 | offsetY = height / 2; 473 | x = (localLeft + offsetX + region.left); 474 | y = (localTop + offsetY + region.top); 475 | point.x = x; 476 | point.y = y; 477 | } 478 | return point; 479 | } 480 | 481 | /** 482 | * 初始化yolo服务 483 | * 484 | * @param yoloServerIp yolo服务器IP。端口固定为9528 485 | * @param modelPath 模型路径 486 | * @param classesPath 种类路径,CPU模式需要此参数 487 | * @return {Promise.} 总是返回true 488 | */ 489 | public boolean initYolo(String yoloServerIp, String modelPath, String classesPath) { 490 | return this.boolCmd("initYolo", yoloServerIp, modelPath, classesPath); 491 | } 492 | 493 | /** 494 | * yolo 495 | * 496 | * @param scale 图片缩放率, 默认为 1.0 原大小。大于1.0放大,小于1.0缩小,不能为负数。 497 | * @return {Promise.<[]>} 失败返回null,成功返回数组形式的识别结果, 0~3目标矩形位置 4目标类别 5置信度 498 | */ 499 | public JSONArray yolo(float scale) { 500 | if (scale <= 0) { 501 | scale = 1.0F; 502 | } 503 | String strRet = this.strCmd("yolo", Float.toString(scale)); 504 | if (StringUtils.isNotBlank(strRet)) { 505 | JSONArray retJson = JSONArray.parse(strRet); 506 | JSONArray jsonArray; 507 | for (int i = 0; i < retJson.size(); i++) { 508 | jsonArray = retJson.getJSONArray(i); 509 | jsonArray.set(0, jsonArray.getFloatValue(0) / scale); 510 | jsonArray.set(1, jsonArray.getFloatValue(1) / scale); 511 | jsonArray.set(2, jsonArray.getFloatValue(2) / scale); 512 | jsonArray.set(3, jsonArray.getFloatValue(3) / scale); 513 | } 514 | return retJson; 515 | } 516 | return null; 517 | } 518 | 519 | /** 520 | * URL请求 521 | * 522 | * @param url 请求的地址 http://www.ai-bot.net 523 | * @param requestType 请求类型,GET或者POST 524 | * @param headers 可选参数,请求头 525 | * @param postData 可选参数,用作POST 提交的数据 526 | * @return {Promise.} 返回请求数据内容 527 | */ 528 | public String urlRequest(String url, String requestType, String headers, String postData) { 529 | return this.strCmd("urlRequest", url, requestType, headers, postData); 530 | } 531 | 532 | /** 533 | * Toast消息提示 534 | * 535 | * @param text 提示的文本 536 | * @param duration 显示时长,最大时长3500毫秒 537 | * @return {Promise.} 返回true 538 | */ 539 | public boolean showToast(String text, float duration) { 540 | return this.boolCmd("showToast", text, Float.toString(duration)); 541 | } 542 | 543 | /** 544 | * 启动App 545 | * 546 | * @param name 包名或者app名称 547 | * @return {Promise.} 成功返回true 失败返回false。非Aibote界面时候调用,需要开启悬浮窗 548 | */ 549 | public boolean startApp(String name) { 550 | return this.boolCmd("startApp", name); 551 | } 552 | 553 | /** 554 | * 判断app是否正在运行(包含前后台) 555 | * 556 | * @param name 包名或者app名称 557 | * @return {Promise.} 正在运行返回true,否则返回false 558 | */ 559 | public boolean appIsRunnig(String name) { 560 | return this.boolCmd("appIsRunnig", name); 561 | } 562 | 563 | /** 564 | * 获取已安装app的包名(不包含系统APP) 565 | * 566 | * @return 成功返回已安装app包名数组(使用 | 分割),失败返回null 567 | */ 568 | public String getInstalledPackages() { 569 | return this.strCmd("getInstalledPackages"); 570 | } 571 | 572 | /** 573 | * 屏幕大小 574 | * 575 | * @return 成功返回屏幕大小使用 | 分割 576 | */ 577 | public String getWindowSize() { 578 | return this.strCmd("getWindowSize"); 579 | } 580 | 581 | /** 582 | * 图片大小 583 | * 584 | * @param imagePath 图片路径 585 | * @return 成功返回 图片大小使用 | 分割 586 | */ 587 | public String getImageSize(String imagePath) { 588 | return this.strCmd("getImageSize", imagePath); 589 | } 590 | 591 | /** 592 | * 获取安卓ID 593 | * 594 | * @return {Promise.} 成功返回安卓手机ID 595 | */ 596 | public String getAndroidId() { 597 | return this.strCmd("getAndroidId"); 598 | } 599 | 600 | /** 601 | * 获取投屏组号 602 | * 603 | * @return {Promise.} 成功返回投屏组号 604 | */ 605 | public String getGroup() { 606 | return this.strCmd("getGroup"); 607 | } 608 | 609 | /** 610 | * 获取投屏编号 611 | * 612 | * @return {Promise.} 成功返回投屏编号 613 | */ 614 | public String getIdentifier() { 615 | return this.strCmd("getIdentifier"); 616 | } 617 | 618 | /** 619 | * 获取投屏标题 620 | * 621 | * @return {Promise.} 成功返回投屏标题 622 | */ 623 | public String getTitle() { 624 | return this.strCmd("getTitle"); 625 | } 626 | 627 | /** 628 | * 识别验证码 629 | * 630 | * @param filePath 图片文件路径 631 | * @param username 用户名 632 | * @param password 密码 633 | * @param softId 软件ID 634 | * @param codeType 图片类型 参考https://www.chaojiying.com/price.html 635 | * @param lenMin 最小位数 默认0为不启用,图片类型为可变位长时可启用这个参数 636 | * @return {Promise.<{err_no:number, err_str:string, pic_id:string, pic_str:string, md5:string}>} 返回JSON 637 | * err_no,(数值) 返回代码 为0 表示正常,错误代码 参考https://www.chaojiying.com/api-23.html 638 | * err_str,(字符串) 中文描述的返回信息 639 | * pic_id,(字符串) 图片标识号,或图片id号 640 | * pic_str,(字符串) 识别出的结果 641 | * md5,(字符串) md5校验值,用来校验此条数据返回是否真实有效 642 | */ 643 | public JSONObject getCaptcha(String filePath, String username, String password, String softId, String codeType, int lenMin) { 644 | String strRet = this.strCmd("getCaptcha", filePath, username, password, softId, codeType, Integer.toString(lenMin)); 645 | return JSONObject.parse(strRet); 646 | } 647 | 648 | /** 649 | * 识别报错返分 650 | * 651 | * @param username 用户名 652 | * @param password 密码 653 | * @param softId 软件ID 654 | * @param picId 图片ID 对应 getCaptcha返回值的pic_id 字段 655 | * @return {Promise.<{err_no:number, err_str:string}>} 返回JSON 656 | * err_no,(数值) 返回代码 657 | * err_str,(字符串) 中文描述的返回信息 658 | */ 659 | public JSONObject errorCaptcha(String username, String password, String softId, String picId) { 660 | String strRet = this.strCmd("errorCaptcha", username, password, softId, picId); 661 | return JSONObject.parse(strRet); 662 | } 663 | 664 | 665 | /** 666 | * 查询验证码剩余题分 667 | * 668 | * @param username 用户名 669 | * @param password 密码 670 | * @return {Promise.<{err_no:number, err_str:string, tifen:string, tifen_lock:string}>} 返回JSON 671 | * err_no,(数值) 返回代码 672 | * err_str,(字符串) 中文描述的返回信息 673 | * tifen,(数值) 题分 674 | * tifen_lock,(数值) 锁定题分 675 | */ 676 | public JSONObject scoreCaptcha(String username, String password) { 677 | String strRet = this.strCmd("scoreCaptcha", username, password); 678 | return JSONObject.parse(strRet); 679 | } 680 | 681 | /** 682 | * 获取元素位置 683 | * 684 | * @param xpath 元素路径 685 | * @return {Promise.<{left:number, top:number, right:number, bottom:number}>} 成功返回元素位置,失败返回null 686 | */ 687 | public Region getElementRect(String xpath) { 688 | String strRet = this.strDelayCmd("getElementRect", xpath); 689 | String[] arrRet = strRet.split("\\|"); 690 | Region region = new Region(); 691 | region.left = Integer.parseInt(arrRet[0]); 692 | region.top = Integer.parseInt(arrRet[1]); 693 | region.right = Integer.parseInt(arrRet[2]); 694 | region.bottom = Integer.parseInt(arrRet[3]); 695 | return region; 696 | } 697 | 698 | /** 699 | * 获取元素描述 700 | * 701 | * @param xpath 元素路径 702 | * @return {Promise.} 成功返回元素内容,失败返回null 703 | */ 704 | public String getElementDescription(String xpath) { 705 | return this.strDelayCmd("getElementDescription", xpath); 706 | } 707 | 708 | /** 709 | * 获取元素文本 710 | * 711 | * @param xpath 元素路径 712 | * @return {Promise.} 成功返回元素内容,失败返回null 713 | */ 714 | public String getElementText(String xpath) { 715 | return this.strCmd("getElementText", xpath); 716 | } 717 | 718 | /** 719 | * 判断元素是否可见 720 | * 721 | * @param xpath 元素路径 722 | * @return {Promise.} 可见 ture,不可见 false 723 | */ 724 | public boolean elementIsVisible(String xpath) { 725 | String windowRect = this.getWindowSize(); 726 | Region elementRect = this.getElementRect(xpath); 727 | if (elementRect == null) return false; 728 | 729 | String[] split = windowRect.split("\\|"); 730 | int elementWidth = elementRect.right - elementRect.left; 731 | int elementHeight = elementRect.bottom - elementRect.top; 732 | if (elementRect.top < 0 || elementRect.left < 0 || elementWidth > Integer.parseInt(split[0]) || elementHeight > Integer.parseInt(split[1])) return false; 733 | else return true; 734 | } 735 | 736 | /** 737 | * 设置元素文本 738 | * 739 | * @param xpath 元素路径 740 | * @param text 设置的文本 741 | * @return {Promise.} 成功返回true 失败返回false 742 | */ 743 | public boolean setElementText(String xpath, String text) { 744 | return this.boolDelayCmd("setElementText", xpath, text); 745 | } 746 | 747 | /** 748 | * 点击元素 749 | * 750 | * @param xpath 元素路径 751 | * @return {Promise.} 成功返回true 失败返回false 752 | */ 753 | public boolean clickElement(String xpath) { 754 | return this.boolDelayCmd("clickElement", xpath); 755 | } 756 | 757 | /** 758 | * 滚动元素 759 | * 760 | * @param xpath 元素路径 761 | * @param direction 0 向前滑动, 1 向后滑动 762 | * @return {Promise.} 成功返回true 失败返回false 763 | */ 764 | public boolean scrollElement(String xpath, int direction) { 765 | return this.boolDelayCmd("scrollElement", xpath, Integer.toString(direction)); 766 | } 767 | 768 | /** 769 | * 判断元素是否存在 770 | * 771 | * @param xpath 元素路径 772 | * @return {Promise.} 成功返回true 失败返回false 773 | */ 774 | public boolean existsElement(String xpath) { 775 | return this.boolCmd("existsElement", xpath); 776 | } 777 | 778 | /** 779 | * 判断元素是否选中 780 | * 781 | * @param xpath 元素路径 782 | * @return {Promise.} 成功返回true 失败返回false 783 | */ 784 | public boolean isSelectedElement(String xpath) { 785 | return this.boolCmd("isSelectedElement", xpath); 786 | } 787 | 788 | /** 789 | * 上传文件 790 | * 791 | * @param windowsFilePath 电脑文件路径,注意电脑路径 "\\"转义问题 792 | * @param androidFilePath 安卓文件保存路径, 安卓外部存储根目录 /storage/emulated/0/ 793 | * @return {Promise.} 成功返回true 失败返回false 794 | */ 795 | public boolean pushFile(String windowsFilePath, String androidFilePath) throws IOException { 796 | byte[] fileData = FileUtils.readFileToByteArray(new File(windowsFilePath)); 797 | return this.sendFile("pushFile", androidFilePath, fileData); 798 | } 799 | 800 | /** 801 | * 拉取文件 802 | * 803 | * @param androidFilePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 804 | * @param windowsFilePath 电脑文件保存路径,注意电脑路径 "\\"转义问题 805 | * @return {Promise.} 806 | */ 807 | public void pullFile(String androidFilePath, String windowsFilePath) throws IOException { 808 | byte[] byteData = this.bytesCmd("pullFile", androidFilePath); 809 | FileUtils.writeByteArrayToFile(new File(windowsFilePath), byteData, true); 810 | } 811 | 812 | /** 813 | * GET 下载url文件 814 | * 815 | * @param {string} url 文件请求地址 816 | * @param {string} savePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 817 | * @return {Promise.} 成功返回true,失败返回 false 818 | */ 819 | public boolean downloadFile(String url, String savePath) { 820 | return this.boolCmd("downloadFile", url, savePath); 821 | } 822 | 823 | /** 824 | * 写入安卓文件 825 | * 826 | * @param androidFilePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 827 | * @param text 写入的内容 828 | * @param {boolean} isAppend 可选参数,是否追加,默认覆盖文件内容 829 | * @return {Promise.} 成功返回true,失败返回 false 830 | */ 831 | public boolean writeAndroidFile(String androidFilePath, String text, boolean isAppend) { 832 | return this.boolCmd("writeAndroidFile", androidFilePath, text, Boolean.toString(isAppend)); 833 | } 834 | 835 | /** 836 | * 读取安卓文件 837 | * 838 | * @param androidFilePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 839 | * @return {Promise.} 成功返回文件内容,失败返回 null 840 | */ 841 | public String readAndroidFile(String androidFilePath) { 842 | return this.strCmd("readAndroidFile", androidFilePath); 843 | } 844 | 845 | /** 846 | * 读取安卓文件 847 | * 848 | * @param androidFilePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 849 | * @return 成功返回文件字节数组,失败返回 null 850 | */ 851 | public byte[] readAndroidFileBytes(String androidFilePath) { 852 | return this.bytesCmd("readAndroidFile", androidFilePath); 853 | } 854 | 855 | /** 856 | * 删除安卓文件 857 | * 858 | * @param androidFilePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 859 | * @return {Promise.} 成功返回true,失败返回 false 860 | */ 861 | public String deleteAndroidFile(String androidFilePath) { 862 | return this.strCmd("deleteAndroidFile", androidFilePath); 863 | } 864 | 865 | /** 866 | * 判断文件是否存在 867 | * 868 | * @param androidFilePath 安卓文件路径,安卓外部存储根目录 /storage/emulated/0/ 869 | * @return {Promise.} 成功返回true,失败返回 false 870 | */ 871 | public String existsAndroidFile(String androidFilePath) { 872 | return this.strCmd("existsAndroidFile", androidFilePath); 873 | } 874 | 875 | /** 876 | * 获取文件夹内的所有文件(不包含深层子目录) 877 | * 878 | * @param androidDirectory 安卓目录,安卓外部存储根目录 /storage/emulated/0/ 879 | * @return 成功返回所有子文件名称,用|分割,失败返回null 880 | */ 881 | public String getAndroidSubFiles(String androidDirectory) { 882 | return this.strCmd("getAndroidSubFiles", androidDirectory); 883 | } 884 | 885 | /** 886 | * 创建安卓文件夹 887 | * 888 | * @param androidDirectory 安卓目录 889 | * @return {Promise.} 成功返回true,失败返回 false 890 | */ 891 | public boolean makeAndroidDir(String androidDirectory) { 892 | return this.boolCmd("makeAndroidDir", androidDirectory); 893 | } 894 | 895 | 896 | /** 897 | * Intent 跳转 898 | * 899 | * @param action 动作,例如 "android.intent.action.VIEW" 900 | * @param uri 跳转链接,可选参数 例如:打开支付宝扫一扫界面,"alipayqr://platformapi/startapp?saId=10000007" 901 | * @param packageName 包名,可选参数 "com.xxx.xxxxx" 902 | * @param className 类名,可选参数 903 | * @param type 类型,可选参数 904 | * @return {Promise.} 成功返回true,失败返回 false 905 | */ 906 | public boolean startActivity(String action, String uri, String packageName, String className, String type) { 907 | return this.boolCmd("startActivity", action, uri, packageName, className, type); 908 | } 909 | 910 | /** 911 | * 拨打电话 912 | * 913 | * @param phoneNumber 拨打的电话号码 914 | * @return {Promise.} 成功返回true,失败返回 false 915 | */ 916 | public boolean callPhone(String phoneNumber) { 917 | return this.boolCmd("callPhone", phoneNumber); 918 | } 919 | 920 | /** 921 | * 发送短信 922 | * 923 | * @param phoneNumber 发送的电话号码 924 | * @param message 短信内容 925 | * @return {Promise.} 成功返回true,失败返回 false 926 | */ 927 | public boolean sendMsg(String phoneNumber, String message) { 928 | return this.boolCmd("sendMsg", phoneNumber, message); 929 | } 930 | 931 | /** 932 | * 获取当前活动窗口(Activity) 933 | * 934 | * @return {Promise.} 成功返回当前activity 935 | */ 936 | public String getActivity() { 937 | return this.strCmd("getActivity"); 938 | } 939 | 940 | /** 941 | * 获取当前活动包名(Package) 942 | * 943 | * @return {Promise.} 成功返回当前包名 944 | */ 945 | public String getPackage() { 946 | return this.strCmd("getPackage"); 947 | } 948 | 949 | /** 950 | * 设置剪切板文本 951 | * 952 | * @param text 设置的文本 953 | * @return {Promise.} 成功返回true,失败返回 false 954 | */ 955 | public boolean setClipboardText(String text) { 956 | return this.boolCmd("setClipboardText", text); 957 | } 958 | 959 | /** 960 | * 获取剪切板文本 961 | * 962 | * @return {Promise.} 需要打开aibote输入法。成功返回剪切板文本,失败返回null 963 | */ 964 | public String getClipboardText() { 965 | return this.strCmd("getClipboardText"); 966 | } 967 | 968 | /** 969 | * 创建TextView控件 970 | * 971 | * @param id 控件ID,不可与其他控件重复 972 | * @param text 控件文本 973 | * @param x 控件在屏幕上x坐标 974 | * @param y 控件在屏幕上y坐标 975 | * @param width 控件宽度 976 | * @param height 控件高度 977 | * @return {Promise.} 成功返回true,失败返回 false 978 | */ 979 | public boolean createTextView(int id, String text, int x, int y, int width, int height) { 980 | return this.boolCmd("createTextView", Integer.toString(id), text, Integer.toString(x), Integer.toString(y), Integer.toString(width), Integer.toString(height)); 981 | } 982 | 983 | /** 984 | * 创建EditText控件 985 | * 986 | * @param id 控件ID,不可与其他控件重复 987 | * @param text 提示文本 988 | * @param x 控件在屏幕上x坐标 989 | * @param y 控件在屏幕上y坐标 990 | * @param width 控件宽度 991 | * @param height 控件高度 992 | * @return {Promise.} 成功返回true,失败返回 false 993 | */ 994 | public boolean createEditText(int id, String text, int x, int y, int width, int height) { 995 | return this.boolCmd("createEditText", Integer.toString(id), text, Integer.toString(x), Integer.toString(y), Integer.toString(width), Integer.toString(height)); 996 | } 997 | 998 | /** 999 | * 创建CheckBox控件 1000 | * 1001 | * @param id 控件ID,不可与其他控件重复 1002 | * @param text 控件文本 1003 | * @param x 控件在屏幕上x坐标 1004 | * @param y 控件在屏幕上y坐标 1005 | * @param width 控件宽度 1006 | * @param height 控件高度 1007 | * @param {boolean} isSelect 是否勾选 1008 | * @return {Promise.} 成功返回true,失败返回 false 1009 | */ 1010 | public boolean createCheckBox(int id, String text, int x, int y, int width, int height, boolean isSelect) { 1011 | return this.boolCmd("createCheckBox", Integer.toString(id), text, Integer.toString(x), Integer.toString(y), Integer.toString(width), Integer.toString(height), Boolean.toString(isSelect)); 1012 | } 1013 | 1014 | /** 1015 | * 创建ListText控件 1016 | * 1017 | * @param id 控件ID,不可与其他控件重复 1018 | * @param text 提示文本 1019 | * @param x 控件在屏幕上x坐标 1020 | * @param y 控件在屏幕上y坐标 1021 | * @param width 控件宽度 1022 | * @param height 控件高度 1023 | * @param {string[]} listText 列表文本 1024 | * @return {Promise.} 成功返回true,失败返回 false 1025 | */ 1026 | public boolean createListText(int id, String text, int x, int y, int width, int height, String listText) { 1027 | return this.boolCmd("createListText", Integer.toString(id), text, Integer.toString(x), Integer.toString(y), Integer.toString(width), Integer.toString(height), listText); 1028 | } 1029 | 1030 | /** 1031 | * 创建WebView控件 1032 | * 1033 | * @param id 控件ID,不可与其他控件重复 1034 | * @param url 加载的链接 1035 | * @param x 控件在屏幕上x坐标,值为-1时自动填充宽高 1036 | * @param y 控件在屏幕上y坐标,值为-1时自动填充宽高 1037 | * @param width 控件宽度,值为-1时自动填充宽高 1038 | * @param height 控件高度,值为-1时自动填充宽高 1039 | * @return {Promise.} 成功返回true,失败返回 false 1040 | */ 1041 | public boolean createWebView(int id, String url, int x, int y, int width, int height) { 1042 | return this.boolCmd("createWebView", Integer.toString(id), url, Integer.toString(x), Integer.toString(y), Integer.toString(width), Integer.toString(height)); 1043 | } 1044 | 1045 | /** 1046 | * 清除脚本控件 1047 | * 1048 | * @return {Promise.} 成功返回true,失败返回 false 1049 | */ 1050 | public boolean clearScriptControl() { 1051 | return this.boolCmd("clearScriptControl"); 1052 | } 1053 | 1054 | /** 1055 | * 获取脚本配置参数 1056 | * 1057 | * @return {Promise.} 成功返回{"id":"text", "id":"isSelect"} 此类对象,失败返回null。函数仅返回TextEdit和CheckBox控件值,需要用户点击安卓端 "提交参数" 按钮 1058 | */ 1059 | public JSONObject getScriptParam() { 1060 | String strRet = this.strCmd("getScriptParam"); 1061 | if (strRet == null) return null; 1062 | else return JSONObject.parse(strRet); 1063 | } 1064 | 1065 | /** 1066 | * 初始化android Accessory,获取手机hid相关的数据。 1067 | * 1068 | * @return boolean 1069 | */ 1070 | public boolean initAccessory() { 1071 | return this.boolCmd("initAccessory"); 1072 | } 1073 | 1074 | //hid 使用 port = 56668;//固定端口 1075 | } 1076 | -------------------------------------------------------------------------------- /sdk-core/src/main/java/net/aibote/sdk/WinBot.java: -------------------------------------------------------------------------------- 1 | package net.aibote.sdk; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.alibaba.fastjson2.JSONArray; 5 | import com.alibaba.fastjson2.JSONObject; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import net.aibote.sdk.dto.OCRResult; 9 | import net.aibote.sdk.dto.Point; 10 | import net.aibote.sdk.options.Mode; 11 | import net.aibote.sdk.options.Region; 12 | import net.aibote.sdk.options.SubColor; 13 | import net.aibote.utils.HttpClientUtils; 14 | import net.aibote.utils.ImageBase64Converter; 15 | import org.apache.commons.lang3.StringUtils; 16 | 17 | import java.util.*; 18 | 19 | @EqualsAndHashCode(callSuper = true) 20 | @Data 21 | public abstract class WinBot extends Aibote { 22 | 23 | /** 24 | * 查找窗口句柄 25 | * 26 | * @param className 窗口类名 27 | * @param windowName 窗口名 28 | * @return String 成功返回窗口句柄,失败返回null 29 | */ 30 | public String findWindow(String className, String windowName) { 31 | return strCmd("findWindow", className, windowName); 32 | } 33 | 34 | /** 35 | * 查找窗口句柄数组, 以 “|” 分割 36 | * 37 | * @param className 窗口类名 38 | * @param windowName 窗口名 39 | * @return String 成功返回窗口句柄,失败返回null 40 | */ 41 | public String findWindows(String className, String windowName) { 42 | return strCmd("findWindows", className, windowName); 43 | } 44 | 45 | /** 46 | * 查找窗口句柄 47 | * 48 | * @param curHwnd 当前窗口句柄 49 | * @param className 窗口类名 50 | * @param windowName 窗口名 51 | * @return String 成功返回窗口句柄,失败返回null 52 | */ 53 | public String findSubWindow(String curHwnd, String className, String windowName) { 54 | return strCmd("findSubWindow", curHwnd, className, windowName); 55 | } 56 | 57 | /** 58 | * 查找父窗口句柄 59 | * 60 | * @param curHwnd 当前窗口句柄 61 | * @return String 成功返回窗口句柄,失败返回null 62 | */ 63 | public String findParentWindow(String curHwnd) { 64 | return strCmd("findParentWindow", curHwnd); 65 | } 66 | 67 | /** 68 | * 查找桌面窗口句柄 69 | * 70 | * @return 成功返回窗口句柄,失败返回null 71 | */ 72 | public String findDesktopWindow() { 73 | return strCmd("findDesktopWindow"); 74 | } 75 | 76 | /** 77 | * 获取窗口名称 78 | * 79 | * @param hwnd 当前窗口句柄 80 | * @return String 成功返回窗口句柄,失败返回null 81 | */ 82 | public String getWindowName(String hwnd) { 83 | return strCmd("getWindowName", hwnd); 84 | } 85 | 86 | /** 87 | * 显示/隐藏窗口 88 | * 89 | * @param hwnd 当前窗口句柄 90 | * @param isShow 是否显示 91 | * @return boolean 成功返回true,失败返回false 92 | */ 93 | public boolean showWindow(String hwnd, boolean isShow) { 94 | return boolCmd("showWindow", hwnd, String.valueOf(isShow)); 95 | } 96 | 97 | /** 98 | * 显示/隐藏窗口 99 | * 100 | * @param hwnd 当前窗口句柄 101 | * @param isTop 是否置顶 102 | * @return boolean 成功返回true,失败返回false 103 | */ 104 | public boolean setWindowTop(String hwnd, boolean isTop) { 105 | return boolCmd("setWindowTop", hwnd, String.valueOf(isTop)); 106 | } 107 | 108 | /** 109 | * 获取窗口位置。 用“|”分割 110 | * 111 | * @param hwnd 当前窗口句柄 112 | * @return 0|0|0|0 113 | */ 114 | public String getWindowPos(String hwnd) { 115 | return strCmd("getWindowPos", hwnd); 116 | } 117 | 118 | /** 119 | * 设置窗口位置 120 | * 121 | * @param hwnd 当前窗口句柄 122 | * @param left 左上角横坐标 123 | * @param top 左上角纵坐标 124 | * @param width width 窗口宽度 125 | * @param height height 窗口高度 126 | * @return boolean 成功返回true 失败返回 false 127 | */ 128 | public boolean setWindowPos(String hwnd, int left, int top, int width, int height) { 129 | return boolCmd("setWindowPos", hwnd, Integer.toString(left), Integer.toString(top), Integer.toString(width), Integer.toString(height)); 130 | } 131 | 132 | /** 133 | * 移动鼠标
134 | * 如果mode值为true且目标控件有单独的句柄,则需要通过getElementWindow获得元素句柄,指定elementHwnd的值(极少应用窗口由父窗口响应消息,则无需指定) 135 | * 136 | * @param hwnd 窗口句柄 137 | * @param x 横坐标 138 | * @param y 纵坐标 139 | * @param mode 操作模式,后台 true,前台 false。默认前台操作。 140 | * @param elementHwnd 元素句柄 141 | * @return boolean 总是返回true 142 | */ 143 | public boolean moveMouse(String hwnd, int x, int y, Mode mode, String elementHwnd) { 144 | return boolCmd("moveMouse", hwnd, Integer.toString(x), Integer.toString(y), mode.boolValueStr(), elementHwnd); 145 | } 146 | 147 | /** 148 | * 移动鼠标(相对坐标) 149 | * 150 | * @param hwnd 窗口句柄 151 | * @param x 相对横坐标 152 | * @param y 相对纵坐标 153 | * @param mode 操作模式,后台 true,前台 false。默认前台操作 154 | * @return boolean 总是返回true 155 | */ 156 | public boolean moveMouseRelative(String hwnd, int x, int y, Mode mode) { 157 | return boolCmd("moveMouseRelative", hwnd, Integer.toString(x), Integer.toString(y), mode.boolValueStr()); 158 | } 159 | 160 | /** 161 | * 滚动鼠标 162 | * 163 | * @param hwnd 窗口句柄 164 | * @param x 横坐标 165 | * @param y 纵坐标 166 | * @param dwData 鼠标滚动次数,负数下滚鼠标,正数上滚鼠标 167 | * @param mode 操作模式,后台 true,前台 false。默认前台操作 168 | * @return boolean 总是返回true 169 | */ 170 | public boolean rollMouse(String hwnd, int x, int y, int dwData, Mode mode) { 171 | return boolCmd("rollMouse", hwnd, Integer.toString(x), Integer.toString(y), Integer.toString(dwData), mode.boolValueStr()); 172 | } 173 | 174 | /** 175 | * 鼠标点击
176 | * 如果mode值为true且目标控件有单独的句柄,则需要通过getElementWindow获得元素句柄,指定elementHwnd的值(极少应用窗口由父窗口响应消息,则无需指定) 177 | * 178 | * @param hwnd 窗口句柄 179 | * @param x 横坐标 180 | * @param y 纵坐标 181 | * @param mouseType 单击左键:1 单击右键:2 按下左键:3 弹起左键:4 按下右键:5 弹起右键:6 双击左键:7 双击右键:8 182 | * @param mode 操作模式,后台 true,前台 false。默认前台操作。 183 | * @param elementHwnd 元素句柄 184 | * @return boolean 总是返回true。 185 | */ 186 | public boolean clickMouse(String hwnd, int x, int y, int mouseType, Mode mode, String elementHwnd) { 187 | return boolCmd("clickMouse", hwnd, Integer.toString(x), Integer.toString(y), Integer.toString(mouseType), mode.boolValueStr(), elementHwnd); 188 | } 189 | 190 | /** 191 | * 输入文本 192 | * 193 | * @param txt 输入的文本 194 | * @return boolean 总是返回true 195 | */ 196 | public boolean sendKeys(String txt) { 197 | return boolCmd("sendKeys", txt); 198 | } 199 | 200 | /** 201 | * 后台输入文本 202 | * 203 | * @param hwnd 窗口句柄,如果目标控件有单独的句柄,需要通过getElementWindow获得句柄 204 | * @param txt 输入的文本 205 | * @return boolean 总是返回true 206 | */ 207 | public boolean sendKeysByHwnd(String hwnd, String txt) { 208 | return boolCmd("sendKeysByHwnd", hwnd, txt); 209 | } 210 | 211 | /** 212 | * 输入虚拟键值(VK) 213 | * 214 | * @param vk VK键值,例如:回车对应 VK键值 13 215 | * @param keyState 按下弹起:1 按下:2 弹起:3 216 | * @return boolean 总是返回true 217 | */ 218 | public boolean sendVk(int vk, int keyState) { 219 | return boolCmd("sendVk", Integer.toString(vk), Integer.toString(keyState)); 220 | } 221 | 222 | /** 223 | * 后台输入虚拟键值(VK) 224 | * 225 | * @param hwnd 窗口句柄,如果目标控件有单独的句柄,需要通过getElementWindow获得句柄 226 | * @param vk VK键值,例如:回车对应 VK键值 13 227 | * @param keyState 按下弹起:1 按下:2 弹起:3 228 | * @return boolean 总是返回true 229 | */ 230 | public boolean sendVkByHwnd(String hwnd, int vk, int keyState) { 231 | return boolCmd("sendVkByHwnd", hwnd, Integer.toString(vk), Integer.toString(keyState)); 232 | } 233 | 234 | /** 235 | * 截图保存。threshold默认保存原图。 236 | * 237 | * @param hwnd 窗口句柄 238 | * @param savePath 保存的位置 239 | * @param region 区域 240 | * @param thresholdType hresholdType算法类型。
241 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0 242 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva 243 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0 244 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变 245 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变 246 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值 247 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 248 | * @param thresh 阈值。 thresh和maxval同为255时灰度处理 249 | * @param maxval 最大值。 thresh和maxval同为255时灰度处理 250 | * @return boolean 251 | */ 252 | public boolean saveScreenshot(String hwnd, String savePath, Region region, int thresholdType, int thresh, int maxval) { 253 | if (thresholdType == 5 || thresholdType == 6) { 254 | thresh = 127; 255 | maxval = 255; 256 | } 257 | return boolCmd("saveScreenshot", hwnd, savePath, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval)); 258 | } 259 | 260 | /** 261 | * 获取指定坐标点的色值 262 | * 263 | * @param hwnd 窗口句柄 264 | * @param x 横坐标 265 | * @param y 纵坐标 266 | * @param mode 操作模式,后台 true,前台 false。默认前台操作 267 | * @return 成功返回#开头的颜色值,失败返回null 268 | */ 269 | public String getColor(String hwnd, int x, int y, boolean mode) { 270 | return strCmd("getColor", hwnd, Integer.toString(x), Integer.toString(y), Boolean.toString(mode)); 271 | } 272 | 273 | /** 274 | * @param hwndOrBigImagePath 窗口句柄或者图片路径 275 | * @param smallImagePath 小图片路径,多张小图查找应当用"|"分开小图路径 276 | * @param region 区域 277 | * @param sim 图片相似度 0.0-1.0,sim默认0.95 278 | * @param thresholdType thresholdType算法类型:
279 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0 280 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva 281 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0 282 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变 283 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变 284 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值 285 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 286 | * @param thresh 阈值。threshold默认保存原图。thresh和maxval同为255时灰度处理 287 | * @param maxval 最大值。threshold默认保存原图。thresh和maxval同为255时灰度处理 288 | * @param multi 找图数量,默认为1 找单个图片坐标 289 | * @param mode 操作模式,后台 true,前台 false。默认前台操作。hwndOrBigImagePath为图片文件,此参数无效 290 | * @return 成功返回 单坐标点[{x:number, y:number}],多坐标点[{x1:number, y1:number}, {x2:number, y2:number}...] 失败返回null 291 | */ 292 | public String findImages(String hwndOrBigImagePath, String smallImagePath, Region region, float sim, int thresholdType, int thresh, int maxval, int multi, Mode mode) { 293 | if (thresholdType == 5 || thresholdType == 6) { 294 | thresh = 127; 295 | maxval = 255; 296 | } 297 | 298 | String strData = null; 299 | if (hwndOrBigImagePath.toString().indexOf(".") == -1) {//在窗口上找图 300 | return strDelayCmd("findImage", hwndOrBigImagePath, smallImagePath, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval), Integer.toString(multi), mode.boolValueStr()); 301 | } else {//在文件上找图 302 | return this.strDelayCmd("findImageByFile", hwndOrBigImagePath, smallImagePath, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval), Integer.toString(multi), mode.boolValueStr()); 303 | } 304 | } 305 | 306 | /** 307 | * 找动态图 308 | * 309 | * @param hwnd 窗口句柄 310 | * @param frameRate 前后两张图相隔的时间,单位毫秒 311 | * @param mode 操作模式,后台 true,前台 false。默认前台操作 312 | * @return 成功返回 单坐标点[{x:number, y:number}],多坐标点[{x1:number, y1:number}, {x2:number, y2:number}...] 失败返回null 313 | */ 314 | public String findAnimation(String hwnd, int frameRate, Region region, Mode mode) { 315 | return strDelayCmd("findAnimation", hwnd, Integer.toString(frameRate), Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), mode.boolValueStr()); 316 | } 317 | 318 | /** 319 | * 查找指定色值的坐标点 320 | * 321 | * @param hwnd 窗口句柄 322 | * @param strMainColor 颜色字符串,必须以 # 开头,例如:#008577; 323 | * @param subColors 辅助定位的其他颜色; 324 | * @param region 在指定区域内找色,默认全屏; 325 | * @param sim 相似度。0.0-1.0,sim默认为1 326 | * @param mode 后台 true,前台 false。默认前台操作。 327 | * @return String 成功返回 x|y 失败返回null 328 | */ 329 | public String findColor(String hwnd, String strMainColor, SubColor[] subColors, Region region, float sim, Mode mode) { 330 | StringBuilder subColorsStr = new StringBuilder(); 331 | if (null != subColors) { 332 | SubColor subColor; 333 | for (int i = 0; i < subColors.length; i++) { 334 | subColor = subColors[i]; 335 | subColorsStr.append(subColor.offsetX).append("/"); 336 | subColorsStr.append(subColor.offsetY).append("/"); 337 | subColorsStr.append(subColor.colorStr); 338 | if (i < subColors.length - 1) { //最后不需要\n 339 | subColorsStr.append("\n"); 340 | } 341 | } 342 | } 343 | 344 | return this.strDelayCmd("findColor", hwnd, strMainColor, subColorsStr.toString(), Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim), mode.boolValueStr()); 345 | } 346 | 347 | /** 348 | * 比较指定坐标点的颜色值 349 | * 350 | * @param hwnd 窗口句柄 351 | * @param mainX 主颜色所在的X坐标 352 | * @param mainY 主颜色所在的Y坐标 353 | * @param mainColorStr 颜色字符串,必须以 # 开头,例如:#008577; 354 | * @param subColors 辅助定位的其他颜色; 355 | * @param region 截图区域 默认全屏 356 | * @param sim 相似度,0-1 的浮点数 357 | * @param mode 操作模式,后台 true,前台 false, 358 | * @return boolean 359 | */ 360 | public boolean compareColor(String hwnd, int mainX, int mainY, String mainColorStr, SubColor[] subColors, Region region, float sim, Mode mode) { 361 | StringBuilder subColorsStr = new StringBuilder(); 362 | if (null != subColors) { 363 | SubColor subColor; 364 | for (int i = 0; i < subColors.length; i++) { 365 | subColor = subColors[i]; 366 | subColorsStr.append(subColor.offsetX).append("/"); 367 | subColorsStr.append(subColor.offsetY).append("/"); 368 | subColorsStr.append(subColor.colorStr); 369 | if (i < subColors.length - 1) { //最后不需要\n 370 | subColorsStr.append("\n"); 371 | } 372 | } 373 | } 374 | return this.boolDelayCmd("compareColor", hwnd, Integer.toString(mainX), Integer.toString(mainY), mainColorStr, subColorsStr.toString(), Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Float.toString(sim), mode.boolValueStr()); 375 | } 376 | 377 | /** 378 | * 提取视频帧 379 | * 380 | * @param videoPath 视频路径 381 | * @param saveFolder 提取的图片保存的文件夹目录 382 | * @param jumpFrame 跳帧,默认为1 不跳帧 383 | * @return boolean 成功返回true,失败返回false 384 | */ 385 | public boolean extractImageByVideo(String videoPath, String saveFolder, int jumpFrame) { 386 | return this.boolCmd("extractImageByVideo", videoPath, saveFolder, Integer.toString(jumpFrame)); 387 | } 388 | 389 | /** 390 | * 裁剪图片 391 | * 392 | * @param imagePath 图片路径 393 | * @param saveFolder 裁剪后保存的图片路径 394 | * @param region 区域 395 | * @return boolean 成功返回true,失败返回false 396 | */ 397 | public boolean cropImage(String imagePath, String saveFolder, Region region) { 398 | return this.boolCmd("cropImage", imagePath, saveFolder, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom)); 399 | } 400 | 401 | /** 402 | * 初始化ocr服务 403 | * 404 | * @param ocrServerIp ocr服务器IP 405 | * @param ocrServerPort ocr服务器端口,固定端口9527。 注意,如果传入的值<=0 ,则都会当默认端口处理。 406 | * @param useAngleModel 支持图像旋转。 默认false。仅内置ocr有效。内置OCR需要安装 407 | * @param enableGPU 启动GPU 模式。默认false 。GPU模式需要电脑安装NVIDIA驱动,并且到群文件下载对应cuda版本 408 | * @param enableTensorrt 启动加速,仅 enableGPU = true 时有效,默认false 。图片太大可能会导致GPU内存不足 409 | * @return boolean 总是返回true 410 | */ 411 | public boolean initOcr(String ocrServerIp, int ocrServerPort, boolean useAngleModel, boolean enableGPU, boolean enableTensorrt) { 412 | //if (ocrServerPort <= 0) { 413 | ocrServerPort = 9527; 414 | //} 415 | return this.boolCmd("initOcr", ocrServerIp, Integer.toString(ocrServerPort), Boolean.toString(useAngleModel), Boolean.toString(enableGPU), Boolean.toString(enableTensorrt)); 416 | } 417 | 418 | /** 419 | * ocr识别 420 | * 421 | * @param hwnd 窗口句柄 422 | * @param region 区域 423 | * @param thresholdType 二值化算法类型 424 | * @param thresh 阈值 425 | * @param maxval 最大值 426 | * @param mode 操作模式,后台 true,前台 false。默认前台操作 427 | * @return String jsonstr 428 | */ 429 | public List ocrByHwnd(String hwnd, Region region, int thresholdType, int thresh, int maxval, Mode mode) { 430 | if (null == region) { 431 | region = new Region(); 432 | } 433 | if (thresholdType == 5 || thresholdType == 6) { 434 | thresh = 127; 435 | maxval = 255; 436 | } 437 | String strRet = this.strCmd("ocrByHwnd", hwnd, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval), mode.boolValueStr()); 438 | if (null == strRet || strRet == "" || strRet == "null" || strRet == "[]") { 439 | return null; 440 | } else { 441 | List list = new ArrayList<>(); 442 | JSONArray jsonArray = JSONArray.parseArray(strRet); 443 | jsonArray.forEach((ary) -> { 444 | if (ary instanceof JSONArray) { 445 | JSONArray a = (JSONArray) ary; 446 | OCRResult ocrResult = new OCRResult(); 447 | ocrResult.lt = new Point(a.getJSONArray(0).getJSONArray(0).getIntValue(0), a.getJSONArray(0).getJSONArray(0).getIntValue(1)); 448 | ocrResult.rt = new Point(a.getJSONArray(0).getJSONArray(1).getIntValue(0), a.getJSONArray(0).getJSONArray(1).getIntValue(1)); 449 | ocrResult.ld = new Point(a.getJSONArray(0).getJSONArray(2).getIntValue(0), a.getJSONArray(0).getJSONArray(2).getIntValue(1)); 450 | ocrResult.rd = new Point(a.getJSONArray(0).getJSONArray(3).getIntValue(0), a.getJSONArray(0).getJSONArray(3).getIntValue(1)); 451 | ocrResult.word = a.getJSONArray(1).getString(0); 452 | ocrResult.rate = a.getJSONArray(1).getFloatValue(1); 453 | 454 | list.add(ocrResult); 455 | } 456 | }); 457 | return list; 458 | } 459 | } 460 | 461 | /** 462 | * ocr识别 463 | * 464 | * @param imagePath 图片路径 465 | * @param region 区域 466 | * @param thresholdType 二值化算法类型 467 | * @param thresh 阈值 468 | * @param maxval 最大值 469 | * @return String jsonstr 470 | */ 471 | public List ocrByFile(String imagePath, Region region, int thresholdType, int thresh, int maxval) { 472 | if (null == region) { 473 | region = new Region(); 474 | } 475 | String strRet = this.strCmd("ocrByFile", imagePath, Integer.toString(region.left), Integer.toString(region.top), Integer.toString(region.right), Integer.toString(region.bottom), Integer.toString(thresholdType), Integer.toString(thresh), Integer.toString(maxval)); 476 | if (strRet == null || strRet == "" || strRet == "null" || strRet == "[]") { 477 | return null; 478 | } else { 479 | List list = new ArrayList<>(); 480 | JSONArray jsonArray = JSONArray.parseArray(strRet); 481 | jsonArray.forEach((ary) -> { 482 | if (ary instanceof JSONArray) { 483 | JSONArray a = (JSONArray) ary; 484 | OCRResult ocrResult = new OCRResult(); 485 | ocrResult.lt = new Point(a.getJSONArray(0).getJSONArray(0).getIntValue(0), a.getJSONArray(0).getJSONArray(0).getIntValue(1)); 486 | ocrResult.rt = new Point(a.getJSONArray(0).getJSONArray(1).getIntValue(0), a.getJSONArray(0).getJSONArray(1).getIntValue(1)); 487 | ocrResult.ld = new Point(a.getJSONArray(0).getJSONArray(2).getIntValue(0), a.getJSONArray(0).getJSONArray(2).getIntValue(1)); 488 | ocrResult.rd = new Point(a.getJSONArray(0).getJSONArray(3).getIntValue(0), a.getJSONArray(0).getJSONArray(3).getIntValue(1)); 489 | ocrResult.word = a.getJSONArray(1).getString(0); 490 | ocrResult.rate = a.getJSONArray(1).getFloatValue(1); 491 | 492 | list.add(ocrResult); 493 | } 494 | }); 495 | return list; 496 | } 497 | } 498 | 499 | /** 500 | * 获取屏幕文字 501 | * 502 | * @param hwndOrImagePath 窗口句柄或者图片路径 503 | * @param region 区域 504 | * @param thresholdType thresholdType算法类型:
505 | * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0
506 | * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva
507 | * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0
508 | * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变
509 | * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变
510 | * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值
511 | * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 512 | * @param thresh 阈值 513 | * @param maxval 最大值 514 | * @param mode 后台 true,前台 false。默认前台操作, 仅适用于hwnd 515 | * @return 失败返回null,成功返窗口上的文字 516 | */ 517 | public String getWords(String hwndOrImagePath, Region region, int thresholdType, int thresh, int maxval, Mode mode) { 518 | if (thresholdType == 5 || thresholdType == 6) { 519 | thresh = 127; 520 | maxval = 255; 521 | } 522 | 523 | List wordsResult = null; 524 | if (hwndOrImagePath.indexOf(".") == -1) { 525 | wordsResult = this.ocrByHwnd(hwndOrImagePath, region, thresholdType, thresh, maxval, mode); 526 | } else { 527 | wordsResult = this.ocrByFile(hwndOrImagePath, region, thresholdType, thresh, maxval); 528 | } 529 | 530 | if (null == wordsResult) { 531 | return null; 532 | } 533 | 534 | StringBuilder words = new StringBuilder(); 535 | wordsResult.forEach((obj) -> { 536 | words.append(obj.word).append("\n"); 537 | }); 538 | 539 | return words.toString(); 540 | } 541 | 542 | /** 543 | * 查找文字 544 | * 545 | * @param hwndOrImagePath 窗口句柄或者图片路径 546 | * @param word 要查找的文字 547 | * @param region 区域 548 | * @param thresholdType 算法类型:
549 | * * 0 THRESH_BINARY算法,当前点值大于阈值thresh时,取最大值maxva,否则设置为0
550 | * * 1 THRESH_BINARY_INV算法,当前点值大于阈值thresh时,设置为0,否则设置为最大值maxva
551 | * * 2 THRESH_TOZERO算法,当前点值大于阈值thresh时,不改变,否则设置为0
552 | * * 3 THRESH_TOZERO_INV算法,当前点值大于阈值thresh时,设置为0,否则不改变
553 | * * 4 THRESH_TRUNC算法,当前点值大于阈值thresh时,设置为阈值thresh,否则不改变
554 | * * 5 ADAPTIVE_THRESH_MEAN_C算法,自适应阈值
555 | * * 6 ADAPTIVE_THRESH_GAUSSIAN_C算法,自适应阈值 556 | * @param thresh 阈值 557 | * @param maxval 最大值 558 | * @param mode 后台 true,前台 false。默认前台操作, 仅适用于hwnd 559 | * @return Point 560 | */ 561 | public Point findWords(String hwndOrImagePath, String word, Region region, int thresholdType, int thresh, int maxval, Mode mode) { 562 | if (thresholdType == 5 || thresholdType == 6) { 563 | thresh = 127; 564 | maxval = 255; 565 | } 566 | 567 | List wordsResult = null; 568 | if (hwndOrImagePath.indexOf(".") == -1) { 569 | wordsResult = this.ocrByHwnd(hwndOrImagePath, region, thresholdType, thresh, maxval, mode); 570 | } else { 571 | wordsResult = this.ocrByFile(hwndOrImagePath, region, thresholdType, thresh, maxval); 572 | } 573 | 574 | if (null == wordsResult) { 575 | return null; 576 | } 577 | 578 | Point point = new Point(-1, -1); 579 | StringBuilder words = new StringBuilder(); 580 | Optional first = wordsResult.stream().filter((y) -> y.word.indexOf(word) != -1).findFirst(); 581 | if (first.isPresent()) { 582 | OCRResult ocrResult = first.get(); 583 | int localLeft, localTop, localRight, localBottom, width, height, wordWidth, offsetX, offsetY, index, x, y; 584 | localLeft = ocrResult.lt.x; 585 | localTop = ocrResult.lt.y; 586 | localRight = ocrResult.ld.x; 587 | localBottom = ocrResult.ld.y; 588 | width = localRight - localLeft; 589 | height = localBottom - localTop; 590 | wordWidth = width / ocrResult.word.length(); 591 | index = ocrResult.word.indexOf(word); 592 | offsetX = wordWidth * (index + words.length() / 2); 593 | offsetY = height / 2; 594 | x = (localLeft + offsetX + region.left); 595 | y = (localTop + offsetY + region.top); 596 | point.x = x; 597 | point.y = y; 598 | } 599 | return point; 600 | } 601 | 602 | /** 603 | * 初始化yolo服务 604 | * 605 | * @param yoloServerIp yolo服务器IP。端口固定为9528 606 | * @param modelPath 模型路径 607 | * @param classesPath 种类路径,CPU模式需要此参数 608 | * @return {Promise.} 总是返回true 609 | */ 610 | public boolean initYolo(String yoloServerIp, String modelPath, String classesPath) { 611 | return this.boolCmd("initYolo", yoloServerIp, modelPath, classesPath); 612 | } 613 | 614 | /** 615 | * yoloByHwnd 616 | * 617 | * @param hwnd 窗口句柄 618 | * @param mode 操作模式,后台 true,前台 false。默认前台操作 619 | * @return {Promise.<[]>} 失败返回null,成功返回数组形式的识别结果 620 | */ 621 | public JSONArray yoloByHwnd(String hwnd, Mode mode) { 622 | String strRet = this.strCmd("yoloByHwnd", hwnd, mode.boolValueStr()); 623 | if (StringUtils.isNotBlank(strRet)) { 624 | return JSONArray.parse(strRet); 625 | } 626 | return null; 627 | } 628 | 629 | /** 630 | * yoloByFile 631 | * 632 | * @param imagePath 图片路径 633 | * @return {Promise.<[]>} 失败返回null,成功返回数组形式的识别结果 634 | */ 635 | public JSONArray yoloByFile(String imagePath) { 636 | String strRet = this.strCmd("yoloByFile", imagePath); 637 | if (StringUtils.isNotBlank(strRet)) { 638 | return JSONArray.parse(strRet); 639 | } 640 | return null; 641 | } 642 | 643 | /** 644 | * 获取指定元素名称 645 | * 646 | * @param hwnd 窗口句柄。如果是java窗口并且窗口句柄和元素句柄不一致,需要使用getElementWindow获取窗口句柄。 647 | * @param xpath 元素路径 getElementWindow参数的xpath,Aibote Tool应当使用正常模式下获取的XPATH路径,不要 “勾选java窗口” 复选按钮。对话框子窗口,需要获取对应的窗口句柄操作 648 | * @return 成功返回元素名称 649 | */ 650 | public String getElementName(String hwnd, String xpath) { 651 | return this.strDelayCmd("getElementName", hwnd, xpath); 652 | } 653 | 654 | /** 655 | * 获取指定元素文本 656 | * 657 | * @param hwnd 窗口句柄 658 | * @param xpath 元素路径 659 | * @return 成功返回元素文本 660 | */ 661 | public String getElementValue(String hwnd, String xpath) { 662 | return this.strDelayCmd("getElementValue", hwnd, xpath); 663 | } 664 | 665 | /** 666 | * 获取指定元素矩形大小 667 | * 668 | * @param hwnd 窗口句柄。如果是java窗口并且窗口句柄和元素句柄不一致,需要使用getElementWindow获取窗口句柄。 669 | *      * getElementWindow参数的xpath,Aibote Tool应当使用正常模式下获取的XPATH路径,不要 “勾选java窗口” 复选按钮。对话框子窗口,需要获取对应的窗口句柄操作 670 | * @param xpath 元素路径 671 | * @return Region 672 | */ 673 | public Region getElementRect(String hwnd, String xpath) { 674 | String retStr = this.strDelayCmd("getElementRect", hwnd, xpath); 675 | 676 | if (null == retStr || retStr == "-1|-1|-1|-1") { 677 | return null; 678 | } 679 | 680 | String[] arrRet = retStr.split("|"); 681 | Region region = new Region(); 682 | region.left = Integer.valueOf(arrRet[0]); 683 | region.top = Integer.valueOf(arrRet[1]); 684 | region.right = Integer.valueOf(arrRet[2]); 685 | region.bottom = Integer.valueOf(arrRet[3]); 686 | return region; 687 | } 688 | 689 | /** 690 | * 获取元素窗口句柄 691 | * 692 | * @param hwnd 窗口句柄 693 | * @param xpath 元素路径 694 | * @return 成功返回元素窗口句柄,失败返回null 695 | */ 696 | public String getElementWindow(String hwnd, String xpath) { 697 | return this.strDelayCmd("getElementWindow", hwnd, xpath); 698 | } 699 | 700 | /** 701 | * 点击元素 702 | * 703 | * @param hwnd 窗口句柄。如果是java窗口并且窗口句柄和元素句柄不一致,需要使用getElementWindow获取窗口句柄。 704 | * getElementWindow参数的xpath,Aibote Tool应当使用正常模式下获取的XPATH路径,不要 “勾选java窗口” 复选按钮。对话框子窗口,需要获取对应的窗口句柄操作 705 | * @param xpath 元素路径 706 | * @param opt 单击左键:1 单击右键:2 按下左键:3 弹起左键:4 按下右键:5 弹起右键:6 双击左键:7 双击右键:8 707 | * @return {Promise.} 成功返回true 失败返回 false 708 | */ 709 | public boolean clickElement(String hwnd, String xpath, String opt) { 710 | return this.boolDelayCmd("clickElement", hwnd, xpath, opt); 711 | } 712 | 713 | /** 714 | * 执行元素默认操作(一般是点击操作) 715 | * 716 | * @param {string|number} hwnd 窗口句柄。 717 | * @param {string} xpath 元素路径 718 | * @return {Promise.} 成功返回true 失败返回 false 719 | */ 720 | public boolean invokeElement(String hwnd, String xpath) { 721 | return this.boolDelayCmd("invokeElement", hwnd, xpath); 722 | } 723 | 724 | /** 725 | * 设置指定元素作为焦点 726 | * 727 | * @param {string|number} hwnd 窗口句柄 728 | * @param {string} xpath 元素路径 729 | * @return {Promise.} 成功返回true 失败返回 false 730 | */ 731 | public boolean setElementFocus(String hwnd, String xpath) { 732 | return this.boolDelayCmd("setElementFocus", hwnd, xpath); 733 | } 734 | 735 | /** 736 | * 设置元素文本 737 | * 738 | * @param hwnd 窗口句柄。如果是java窗口并且窗口句柄和元素句柄不一致,需要使用getElementWindow获取窗口句柄。 739 | * getElementWindow参数的xpath,Aibote Tool应当使用正常模式下获取的XPATH路径,不要 “勾选java窗口” 复选按钮。对话框子窗口,需要获取对应的窗口句柄操作 740 | * @param xpath 元素路径 741 | * @param value 要设置的内容 742 | * @return {Promise.} 成功返回true 失败返回 false 743 | */ 744 | public boolean setElementValue(String hwnd, String xpath, String value) { 745 | return this.boolDelayCmd("setElementValue", hwnd, xpath, value); 746 | } 747 | 748 | /** 749 | * 滚动元素 750 | * 751 | * @param hwnd 窗口句柄 752 | * @param xpath 元素路径 753 | * @param horizontalPercent 水平百分比 -1不滚动 754 | * @param verticalPercent 垂直百分比 -1不滚动 755 | * @return 成功返回true 失败返回 false 756 | */ 757 | public boolean setElementScroll(String hwnd, String xpath, float horizontalPercent, float verticalPercent) { 758 | return this.boolDelayCmd("setElementScroll", hwnd, xpath, Float.toString(horizontalPercent), Float.toString(verticalPercent)); 759 | } 760 | 761 | /** 762 | * 单/复选框是否选中 763 | * 764 | * @param hwnd 窗口句柄 765 | * @param xpath 元素路径 766 | * @return 成功返回true 失败返回 false 767 | */ 768 | public boolean isSelected(String hwnd, String xpath) { 769 | String strRet = this.strDelayCmd("isSelected", hwnd, xpath); 770 | if ("selected".equals(strRet)) return true; 771 | else return false; 772 | } 773 | 774 | /** 775 | * 关闭窗口 776 | * 777 | * @param hwnd 窗口句柄 778 | * @param xpath 元素路径 779 | * @return 成功返回true 失败返回 false 780 | */ 781 | public boolean closeWindow(String hwnd, String xpath) { 782 | return boolCmd("closeWindow", hwnd, xpath); 783 | } 784 | 785 | /** 786 | * 设置窗口状态 787 | * 788 | * @param hwnd hwnd 窗口句柄。如果是java窗口并且窗口句柄和元素句柄不一致,需要使用getElementWindow获取窗口句柄。 789 | * getElementWindow参数的xpath,Aibote Tool应当使用正常模式下获取的XPATH路径,不要 “勾选java窗口” 复选按钮。对话框子窗口,需要获取对应的窗口句柄操作 790 | * @param xpath 元素路径 791 | * @param state 0正常 1最大化 2 最小化 792 | * @return boolean 793 | */ 794 | public boolean setWindowState(String hwnd, String xpath, int state) { 795 | return boolCmd("setWindowState", hwnd, xpath, Integer.toString(state)); 796 | } 797 | 798 | /** 799 | * 设置剪贴板 800 | * 801 | * @param text 文字内容 802 | * @return boolean 803 | */ 804 | public boolean setClipboardText(String text) { 805 | return boolCmd("setClipboardText", text); 806 | } 807 | 808 | /** 809 | * 获取剪贴板内容 810 | * 811 | * @return 812 | */ 813 | public String getClipboardText() { 814 | return strCmd("getClipboardText"); 815 | } 816 | 817 | /** 818 | * 启动指定程序 819 | * 820 | * @param commandLine 启动命令行 821 | * @param showWindow 是否显示窗口。可选参数,默认显示窗口 822 | * @param isWait 是否等待程序结束。可选参数,默认不等待 823 | * @return {Promise.} 成功返回true,失败返回false 824 | */ 825 | public boolean startProcess(String commandLine, boolean showWindow, boolean isWait) { 826 | return boolCmd("startProcess", commandLine, Boolean.toString(showWindow), Boolean.toString(isWait)); 827 | } 828 | 829 | /** 830 | * 执行cmd命令 831 | * 832 | * @param command cmd命令,不能含 "cmd"字串 833 | * @param waitTimeout 可选参数,等待结果返回超时,单位毫秒,默认300毫秒 834 | * @return {Promise.} 返回cmd执行结果 835 | */ 836 | public String executeCommand(String command, int waitTimeout) { 837 | return strCmd("executeCommand", command, Integer.toString(waitTimeout)); 838 | } 839 | 840 | /** 841 | * 指定url下载文件 842 | * 843 | * @param url 文件地址 844 | * @param filePath 文件保存的路径 845 | * @param isWait 是否等待.为true时,等待下载完成 846 | * @return {Promise.} 总是返回true 847 | */ 848 | public boolean downloadFile(String url, String filePath, boolean isWait) { 849 | return boolCmd("downloadFile", url, filePath, Boolean.toString(isWait)); 850 | } 851 | 852 | /** 853 | * 打开excel文档 854 | * 855 | * @param excelPath excle路径 856 | * @return {Promise.} 成功返回excel对象,失败返回null 857 | */ 858 | public JSONObject openExcel(String excelPath) { 859 | String strRet = strCmd("openExcel", excelPath); 860 | return JSON.parseObject(strRet); 861 | } 862 | 863 | /** 864 | * 打开excel表格 865 | * 866 | * @param excelObject excel对象 867 | * @param sheetName 表名 868 | * @return {Promise.} 成功返回sheet对象,失败返回null 869 | */ 870 | public JSONObject openExcelSheet(JSONObject excelObject, String sheetName) { 871 | String strRet = strCmd("openExcelSheet", excelObject.getString("book"), excelObject.getString("path"), sheetName); 872 | return JSON.parseObject(strRet); 873 | } 874 | 875 | /** 876 | * 保存excel文档 877 | * 878 | * @param excelObject excel对象 879 | * @return {Promise.} 成功返回true,失败返回false 880 | */ 881 | public boolean saveExcel(JSONObject excelObject) { 882 | return boolCmd("saveExcel", excelObject.getString("book"), excelObject.getString("path")); 883 | } 884 | 885 | /** 886 | * 写入数字到excel表格 887 | * 888 | * @param sheetObject sheet对象 889 | * @param row 行 890 | * @param col 列 891 | * @param value 写入的值 892 | * @return {Promise.} 成功返回true,失败返回false 893 | */ 894 | public boolean writeExcelNum(JSONObject sheetObject, int row, int col, int value) { 895 | return boolCmd("writeExcelNum", sheetObject.toJSONString(), Integer.toString(row), Integer.toString(col), Integer.toString(value)); 896 | } 897 | 898 | /** 899 | * 写入字符串到excel表格 900 | * 901 | * @param sheetObject sheet对象 902 | * @param row 行 903 | * @param col 列 904 | * @param strValue 写入的值 905 | * @return {Promise.} 成功返回true,失败返回false 906 | */ 907 | public boolean writeExcelStr(JSONObject sheetObject, int row, int col, String strValue) { 908 | return boolCmd("writeExcelStr", sheetObject.toJSONString(), Integer.toString(row), Integer.toString(col), strValue); 909 | } 910 | 911 | /** 912 | * 读取excel表格数字 913 | * 914 | * @param sheetObject sheet对象 915 | * @param row 行 916 | * @param col 列 917 | * @return {Promise.} 返回读取到的数字 918 | */ 919 | public Float readExcelNum(JSONObject sheetObject, int row, int col) { 920 | String strRet = strCmd("readExcelNum", sheetObject.toJSONString(), Integer.toString(row), Integer.toString(col)); 921 | return Float.valueOf(strRet); 922 | } 923 | 924 | /** 925 | * 读取excel表格数字 926 | * 927 | * @param sheetObject sheet对象 928 | * @param row 行 929 | * @param col 列 930 | * @return {Promise.} 返回读取到的数字 931 | */ 932 | public String readExcelStr(JSONObject sheetObject, int row, int col) { 933 | return strCmd("readExcelStr", sheetObject.toJSONString(), Integer.toString(row), Integer.toString(col)); 934 | } 935 | 936 | /** 937 | * 删除excel表格行 938 | * 939 | * @param sheetObject sheet对象 940 | * @param rowFirst 起始行 941 | * @param rowLast 结束行 942 | * @return {Promise.} 成功返回true,失败返回false 943 | */ 944 | public boolean removeExcelRow(JSONObject sheetObject, int rowFirst, int rowLast) { 945 | return boolCmd("removeExcelRow", sheetObject.toJSONString(), Integer.toString(rowFirst), Integer.toString(rowLast)); 946 | } 947 | 948 | /** 949 | * 删除excel表格列 950 | * 951 | * @param sheetObject sheet对象 952 | * @param rowFirst 起始列 953 | * @param rowLast 结束列 954 | * @return {Promise.} 成功返回true,失败返回false 955 | */ 956 | public boolean removeExcelCol(JSONObject sheetObject, int rowFirst, int rowLast) { 957 | return boolCmd("removeExcelCol", sheetObject.toJSONString(), Integer.toString(rowFirst), Integer.toString(rowLast)); 958 | } 959 | 960 | /** 961 | * 识别验证码 962 | * 963 | * @param filePath 图片文件路径 964 | * @param username 用户名 965 | * @param password 密码 966 | * @param softId 软件ID 967 | * @param codeType 图片类型 参考https://www.chaojiying.com/price.html 968 | * @param lenMin 最小位数 默认0为不启用,图片类型为可变位长时可启用这个参数 969 | * @return {Promise.<{err_no:number, err_str:string, pic_id:string, pic_str:string, md5:string}>} 返回JSON 970 | * err_no,(数值) 返回代码 为0 表示正常,错误代码 参考https://www.chaojiying.com/api-23.html 971 | * err_str,(字符串) 中文描述的返回信息 972 | * pic_id,(字符串) 图片标识号,或图片id号 973 | * pic_str,(字符串) 识别出的结果 974 | * md5,(字符串) md5校验值,用来校验此条数据返回是否真实有效 975 | */ 976 | public JSONObject getCaptcha(String filePath, String username, String password, String softId, String codeType, String lenMin) throws Exception { 977 | if (StringUtils.isBlank(lenMin)) { 978 | lenMin = "0"; 979 | } 980 | 981 | String file_base64 = ImageBase64Converter.convertFileToBase64(filePath); 982 | 983 | String url = "http://upload.chaojiying.net/Upload/Processing.php"; 984 | JSONObject dataJsonObject = new JSONObject(); 985 | dataJsonObject.put("user", username); 986 | dataJsonObject.put("pass", password); 987 | dataJsonObject.put("softid", softId); 988 | dataJsonObject.put("codetype", codeType); 989 | dataJsonObject.put("len_min", lenMin); 990 | dataJsonObject.put("file_base64", file_base64); 991 | 992 | JSONObject paramJsonObject = new JSONObject(); 993 | paramJsonObject.put("multipart", true); 994 | paramJsonObject.put("data", dataJsonObject); 995 | 996 | Map headers = new HashMap<>(); 997 | headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"); 998 | headers.put("Content-Type", "application/x-www-form-urlencoded"); 999 | 1000 | String retStr = HttpClientUtils.doPost(url, paramJsonObject.toJSONString(), headers); 1001 | return JSONObject.parseObject(retStr); 1002 | 1003 | } 1004 | 1005 | /** 1006 | * 识别报错返分 1007 | * 1008 | * @param username 用户名 1009 | * @param password 密码 1010 | * @param softId 软件ID 1011 | * @param picId 图片ID 对应 getCaptcha返回值的pic_id 字段 1012 | * @return {Promise.<{err_no:number, err_str:string}>} 返回JSON 1013 | * err_no,(数值) 返回代码 1014 | * err_str,(字符串) 中文描述的返回信息 1015 | */ 1016 | public JSONObject errorCaptcha(String username, String password, String softId, String picId) throws Exception { 1017 | 1018 | String url = "http://upload.chaojiying.net/Upload/ReportError.php"; 1019 | JSONObject dataJsonObject = new JSONObject(); 1020 | dataJsonObject.put("user", username); 1021 | dataJsonObject.put("pass", password); 1022 | dataJsonObject.put("softid", softId); 1023 | dataJsonObject.put("id", picId); 1024 | 1025 | JSONObject paramJsonObject = new JSONObject(); 1026 | paramJsonObject.put("multipart", true); 1027 | paramJsonObject.put("data", dataJsonObject); 1028 | 1029 | Map headers = new HashMap<>(); 1030 | headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"); 1031 | headers.put("Content-Type", "application/x-www-form-urlencoded"); 1032 | 1033 | String retStr = HttpClientUtils.doPost(url, paramJsonObject.toJSONString(), headers); 1034 | return JSONObject.parseObject(retStr); 1035 | } 1036 | 1037 | /** 1038 | * 查询验证码剩余题分 1039 | * 1040 | * @param username 用户名 1041 | * @param password 密码 1042 | * @return {Promise.<{err_no:number, err_str:string, tifen:string, tifen_lock:string}>} 返回JSON 1043 | * err_no,(数值) 返回代码 1044 | * err_str,(字符串) 中文描述的返回信息 1045 | * tifen,(数值) 题分 1046 | * tifen_lock,(数值) 锁定题分 1047 | */ 1048 | public JSONObject scoreCaptcha(String username, String password) throws Exception { 1049 | 1050 | String url = "http://upload.chaojiying.net/Upload/GetScore.php"; 1051 | JSONObject dataJsonObject = new JSONObject(); 1052 | dataJsonObject.put("user", username); 1053 | dataJsonObject.put("pass", password); 1054 | 1055 | JSONObject paramJsonObject = new JSONObject(); 1056 | paramJsonObject.put("multipart", true); 1057 | paramJsonObject.put("data", dataJsonObject); 1058 | 1059 | Map headers = new HashMap<>(); 1060 | headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"); 1061 | headers.put("Content-Type", "application/x-www-form-urlencoded"); 1062 | 1063 | String retStr = HttpClientUtils.doPost(url, paramJsonObject.toJSONString(), headers); 1064 | return JSONObject.parseObject(retStr); 1065 | } 1066 | 1067 | /** 1068 | * 初始化语音服务(不支持win7) 1069 | * 1070 | * @param speechKey, 微软语音API密钥 1071 | * @param speechRegion, 区域 1072 | * @return {Promise.} 成功返回true 失败返回false 1073 | */ 1074 | public boolean removeExcelCol(String speechKey, String speechRegion) { 1075 | return boolCmd("initSpeechService", speechKey, speechRegion); 1076 | } 1077 | 1078 | /** 1079 | * 音频文件转文本 1080 | * 1081 | * @param filePath, 音频文件路径 1082 | * @param language, 语言,参考开发文档 语言和发音人 1083 | * @return {Promise.} 成功返回转换后的音频文本,失败返回null 1084 | */ 1085 | public String audioFileToText(String filePath, String language) { 1086 | return strCmd("audioFileToText", filePath, language); 1087 | } 1088 | 1089 | /** 1090 | * 麦克风输入流转换文本 1091 | * 1092 | * @param language, 语言,参考开发文档 语言和发音人 1093 | * @return {Promise.} 成功返回转换后的音频文本,失败返回null 1094 | */ 1095 | public String microphoneToText(String language) { 1096 | return strCmd("microphoneToText", language); 1097 | } 1098 | 1099 | /** 1100 | * 文本合成音频到扬声器 1101 | * 1102 | * @param ssmlPathOrText,要转换语音的文本或者".xml"格式文件路径 1103 | * @param language,语言,参考开发文档 语言和发音人 1104 | * @param voiceName,发音人,参考开发文档 语言和发音人 1105 | * @return {Promise.} 成功返回true,失败返回false 1106 | */ 1107 | public boolean textToBullhorn(String ssmlPathOrText, String language, String voiceName) { 1108 | return boolCmd("textToBullhorn", ssmlPathOrText, language, voiceName); 1109 | } 1110 | 1111 | /** 1112 | * 文本合成音频并保存到文件 1113 | * 1114 | * @param ssmlPathOrText,要转换语音的文本或者".xml"格式文件路径 1115 | * @param language,语言,参考开发文档 语言和发音人 1116 | * @param voiceName,发音人,参考开发文档 语言和发音人 1117 | * @param audioPath,保存音频文件路径 1118 | * @return {Promise.} 成功返回true,失败返回false 1119 | */ 1120 | public boolean textToAudioFile(String ssmlPathOrText, String language, String voiceName, String audioPath) { 1121 | return boolCmd("textToAudioFile", ssmlPathOrText, language, voiceName); 1122 | } 1123 | 1124 | /** 1125 | * 麦克风音频翻译成目标语言文本 1126 | * 1127 | * @param sourceLanguage,要翻译的语言,参考开发文档 语言和发音人 1128 | * @param targetLanguage,翻译后的语言,参考开发文档 语言和发音人 1129 | * @return {Promise.} 成功返回翻译后的语言文本,失败返回null 1130 | */ 1131 | public String microphoneTranslationText(String sourceLanguage, String targetLanguage) { 1132 | return strCmd("microphoneTranslationText", sourceLanguage, targetLanguage); 1133 | } 1134 | 1135 | /** 1136 | * 音频文件翻译成目标语言文本 1137 | * 1138 | * @param audioPath, 要翻译的音频文件路径 1139 | * @param sourceLanguage,要翻译的语言,参考开发文档 语言和发音人 1140 | * @param targetLanguage,翻译后的语言,参考开发文档 语言和发音人 1141 | * @return {Promise.}成功返回翻译后的语言文本,失败返回null 1142 | */ 1143 | public String audioFileTranslationText(String audioPath, String sourceLanguage, String targetLanguage) { 1144 | return strCmd("audioFileTranslationText", audioPath, sourceLanguage, targetLanguage); 1145 | } 1146 | 1147 | /** 1148 | * 初始化数字人,第一次初始化需要一些时间 1149 | * 1150 | * @param metahumanModePath, 数字人模型路径 1151 | * @param metahumanScaleValue, 数字人缩放倍数,1为原始大小。为0.5时放大一倍,2则缩小一半 1152 | * @param isUpdateMetahuman, 是否强制更新,默认fasle。为true时强制更新会拖慢初始化速度 1153 | * @return {Promise.} 成功返回true,失败返回false 1154 | */ 1155 | public boolean initMetahuman(String metahumanModePath, float metahumanScaleValue, boolean isUpdateMetahuman, boolean enableRandomImage) { 1156 | return boolCmd("initMetahuman", metahumanModePath, Float.toString(metahumanScaleValue), Boolean.toString(isUpdateMetahuman), Boolean.toString(enableRandomImage)); 1157 | } 1158 | 1159 | /** 1160 | * 数字人说话,此函数需要调用 initSpeechService 初始化语音服务 1161 | * 1162 | * @param saveVoiceFolder, 保存的发音文件目录,文件名以0开始依次增加,扩展为.wav格式 1163 | * @param text 要转换语音的文本 1164 | * @param language 语言,参考开发文档 语言和发音人 1165 | * @param voiceName 发音人,参考开发文档 语言和发音人 1166 | * @param quality 音质,0低品质 1中品质 2高品质, 默认为0低品质 1167 | * @param waitPlaySound 等待音频播报完毕,true等待/false不等待 1168 | * @param speechRate 语速,默认为0,取值范围 -100 至 200 1169 | * @param voiceStyle 语音风格,默认General常规风格,其他风格参考开发文档 语言和发音人 1170 | * @return {Promise.} 成功返回true,失败返回false 1171 | */ 1172 | public boolean metahumanSpeech(String saveVoiceFolder, String text, String language, String voiceName, int quality, boolean waitPlaySound, int speechRate, String voiceStyle) { 1173 | if (StringUtils.isBlank(voiceStyle)) { 1174 | voiceStyle = "General"; 1175 | } 1176 | return this.boolCmd("metahumanSpeech", saveVoiceFolder, text, language, voiceName, Integer.toString(quality), Boolean.toString(waitPlaySound), Integer.toString(speechRate), voiceStyle); 1177 | } 1178 | 1179 | /** 1180 | * 数字人说话缓存模式,需要调用 initSpeechService 初始化语音服务。函数一般用于常用的话术播报,非常用话术切勿使用,否则内存泄漏 1181 | * 1182 | * @param saveVoiceFolder 保存的发音文件目录,文件名以0开始依次增加,扩展为.wav格式 1183 | * @param text 要转换语音的文本 1184 | * @param language 语言,参考开发文档 语言和发音人 1185 | * @param voiceName 发音人,参考开发文档 语言和发音人 1186 | * @param quality 音质,0低品质 1中品质 2高品质, 默认为0低品质 1187 | * @param waitPlaySound 等待音频播报完毕,true等待/false不等待 1188 | * @param speechRate 语速,默认为0,取值范围 -100 至 200 1189 | * @param voiceStyle 语音风格,默认General常规风格,其他风格参考开发文档 语言和发音人 1190 | * @return {Promise.} 成功返回true,失败返回false 1191 | */ 1192 | public boolean metahumanSpeechCache(String saveVoiceFolder, String text, String language, String voiceName, int quality, boolean waitPlaySound, int speechRate, String voiceStyle) { 1193 | if (StringUtils.isBlank(voiceStyle)) { 1194 | voiceStyle = "General"; 1195 | } 1196 | return this.boolCmd("metahumanSpeechCache", saveVoiceFolder, text, language, voiceName, Integer.toString(quality), Boolean.toString(waitPlaySound), Integer.toString(speechRate), voiceStyle); 1197 | } 1198 | 1199 | 1200 | /** 1201 | * 数字人说话文件缓存模式 1202 | * 1203 | * @param {string} audioPath, 音频路径, 同名的 .lab文件需要和音频文件在同一目录下 1204 | * @param {boolean} waitPlaySound,等待音频播报完毕, true等待/false不等待 1205 | * @return {Promise.} 成功返回true,失败返回false 1206 | */ 1207 | public boolean metahumanSpeechByFile(String audioPath, boolean waitPlaySound) { 1208 | return boolCmd("metahumanSpeechByFile", audioPath, Boolean.toString(waitPlaySound)); 1209 | } 1210 | 1211 | 1212 | /** 1213 | * 数字人说话文件缓存模式(Ex) metahumanSpeechByFileEx 不能与 PlayAudioEx 同步执行 1214 | * 1215 | * @param {string} audioPath, 音频路径, 同名的 .lab文件需要和音频文件在同一目录下。若.lab文件不存在,则自动生成.lab文件。生成.lab文件产生的费用,请联系管理员 1216 | * @param {boolean} enableRandomParam, 是否启用随机去重参数 1217 | * @param {boolean} waitPlaySound,等待音频播报完毕,默认为 false等待。为false时 多次调用此函数会添加到队列按顺序播报 1218 | * @return {Promise.} 成功返回true,失败返回false 1219 | */ 1220 | public boolean metahumanSpeechByFileEx(String audioPath, boolean enableRandomParam, boolean waitPlaySound) { 1221 | return boolCmd("metahumanSpeechByFileEx", audioPath, Boolean.toString(enableRandomParam), Boolean.toString(waitPlaySound)); 1222 | } 1223 | 1224 | /** 1225 | * ` 1226 | * 打断数字人说话,一般用作人机对话场景。 1227 | * metahumanSpeech和metahumanSpeechCache的 waitPlaySound 参数 设置为false时,此函数才有意义 1228 | * 1229 | * @return {Promise.} 返回true打断正在说话, 返回false 则为未说话状态 1230 | */ 1231 | public boolean metahumanSpeechBreak() { 1232 | return boolCmd("metahumanSpeechBreak"); 1233 | } 1234 | 1235 | /** 1236 | * 数字人插入视频 1237 | * 1238 | * @param videoFilePath 插入的视频文件路径 1239 | * @param audioFilePath 插入的视频文件路径 1240 | * @param audioFilePath, 插入的音频文件路径 1241 | * @param waitPlayVideo 等待视频播放完毕,true等待/false不等待 1242 | * @return 1243 | */ 1244 | public boolean metahumanInsertVideo(String videoFilePath, String audioFilePath, boolean waitPlayVideo) { 1245 | return boolCmd("metahumanInsertVideo", videoFilePath, audioFilePath, Boolean.toString(waitPlayVideo)); 1246 | } 1247 | 1248 | /** 1249 | * 替换数字人背景 1250 | * 1251 | * @param bgFilePath 数字人背景 图片/视频 路径。仅替换绿幕背景的数字人模型 1252 | * @param replaceRed 数字人背景的三通道之一的 R通道色值。默认-1 自动提取 1253 | * @param replaceGreen 数字人背景的三通道之一的 G通道色值。默认-1 自动提取 1254 | * @param replaceBlue 数字人背景的三通道之一的 B通道色值。默认-1 自动提取 1255 | * @param simValue 相似度。 默认为0,此处参数用作微调RBG值。取值应当大于等于0 1256 | * @return {Promise.} 总是返回true。此函数依赖 initMetahuman函数运行,否则程序会崩溃 1257 | */ 1258 | public boolean replaceBackground(String bgFilePath, int replaceRed, int replaceGreen, int replaceBlue, int simValue) { 1259 | return boolCmd("replaceBackground", bgFilePath, Integer.toString(replaceRed), Integer.toString(replaceGreen), Integer.toString(replaceBlue), Integer.toString(simValue)); 1260 | } 1261 | 1262 | /** 1263 | * 显示数字人说话的文本 1264 | * 1265 | * @param originY 第一个字显示的起始Y坐标点。 默认0 自适应高度 1266 | * @param fontType 字体样式,支持操作系统已安装的字体。例如"Arial"、"微软雅黑"、"楷体" 1267 | * @param fontSize 字体的大小。默认30 1268 | * @param fontRed 字体颜色三通道之一的 R通道色值。默认可填入 128 1269 | * @param fontGreen 字体颜色三通道之一的 G通道色值。默认可填入 255 1270 | * @param fontBlue 字体颜色三通道之一的 B通道色值。默认可填入 0 1271 | * @param italic 是否斜体,默认false 1272 | * @param underline 是否有下划线,默认false 1273 | * @return {Promise.} 总是返回true。此函数依赖 initMetahuman函数运行,否则程序会崩溃 1274 | */ 1275 | public boolean showSpeechText(int originY, String fontType, int fontSize, int fontRed, int fontGreen, int fontBlue, boolean italic, boolean underline) { 1276 | return boolCmd("showSpeechText", Integer.toString(originY), fontType, Integer.toString(fontSize), Integer.toString(fontRed), Integer.toString(fontGreen), Integer.toString(fontBlue), Boolean.toString(italic), Boolean.toString(underline)); 1277 | } 1278 | 1279 | /** 1280 | * 生成数字人短视频,此函数需要调用 initSpeechService 初始化语音服务 1281 | * 1282 | * @param saveVideoFolder, 保存的视频目录 1283 | * @param text 要转换语音的文本 1284 | * @param language 语言,参考开发文档 语言和发音人 1285 | * @param voiceName 发音人,参考开发文档 语言和发音人 1286 | * @param bgFilePath 数字人背景 图片/视频 路径,扣除绿幕会自动获取绿幕的RGB值,null 则不替换背景。仅替换绿幕背景的数字人模型 1287 | * @param simValue 相似度,默认为0。此处参数用作绿幕扣除微调RBG值。取值应当大于等于0 1288 | * @param voiceStyle 语音风格,默认General常规风格,其他风格参考开发文档 语言和发音人 1289 | * @param quality 音质,0低品质 1中品质 2高品质, 默认为0低品质 1290 | * @param speechRate 语速,默认为0,取值范围 -100 至 200 1291 | * @return {Promise.} 成功返回true,失败返回false 1292 | */ 1293 | public boolean makeMetahumanVideoClone(String saveVideoFolder, String text, String language, String voiceName, String bgFilePath, int simValue, String voiceStyle, int quality, int speechRate) { 1294 | return boolCmd("makeMetahumanVideoClone", saveVideoFolder, text, language, voiceName, bgFilePath, Integer.toString(simValue), voiceStyle, Integer.toString(quality), Integer.toString(speechRate)); 1295 | } 1296 | 1297 | /** 1298 | * 打断数字人说话,一般用作人机对话场景。 1299 | * metahumanSpeech和metahumanSpeechCache的 waitPlaySound 参数 设置为false时,此函数才有意义 1300 | * 1301 | * @return {Promise.} 总是返回true 1302 | */ 1303 | public boolean metahumanSpeechBreak() { 1304 | return boolCmd("metahumanSpeechBreak"); 1305 | } 1306 | 1307 | /** 1308 | * 数字人说话文件缓存模式 1309 | * 1310 | * @param {string} audioPath, 音频路径, 同名的 .lab文件需要和音频文件在同一目录下 1311 | * @param {boolean} waitPlaySound,等待音频播报完毕 1312 | * @return {Promise.} 成功返回true,失败返回false 1313 | */ 1314 | public boolean metahumanSpeechByFile(String audioPath, boolean waitPlaySound) { 1315 | return boolCmd("metahumanSpeechByFile", audioPath, Boolean.toString(waitPlaySound)); 1316 | } 1317 | 1318 | /** 1319 | * 初始化数字人声音克隆服务 1320 | * 1321 | * @param {string} apiKey, API密钥 1322 | * @param {string} voiceId, 声音ID 1323 | * @return {Promise.} 成功返回true,失败返回false 1324 | */ 1325 | public boolean initSpeechCloneService(String apiKey, String voiceId) { 1326 | return boolCmd("initSpeechCloneService", apiKey, voiceId); 1327 | } 1328 | 1329 | /** 1330 | * 数字人使用克隆声音说话,此函数需要调用 initSpeechCloneService 初始化语音服务 1331 | * 1332 | * @param {string} saveAudioPath, 保存的发音文件路径。这里是路径,不是目录! 1333 | * @param {string} text,要转换语音的文本 1334 | * @param {string} language,语言,中文:zh-cn,其他语言:other-languages 1335 | * @param {boolean} waitPlaySound,等待音频播报完毕, 1336 | * @return {Promise.} 成功返回true,失败返回false 1337 | */ 1338 | public boolean metahumanSpeechClone(String saveAudioPath, String text, String language, String waitPlaySound) { 1339 | return boolCmd("metahumanSpeechClone", saveAudioPath, text, language, waitPlaySound); 1340 | } 1341 | 1342 | /** 1343 | * 使用克隆声音生成数字人短视频,此函数需要调用 initSpeechCloneService 初始化语音服务 1344 | * 1345 | * @param {string} saveVideoFolder, 保存的视频和音频文件目录 1346 | * @param {string} text,要转换语音的文本 1347 | * @param {string} language,语言,中文:zh-cn,其他语言:other-languages 1348 | * @param {string} bgFilePath,数字人背景 图片/视频 路径,扣除绿幕会自动获取绿幕的RGB值,null 则不替换背景。仅替换绿幕背景的数字人模型 1349 | * @param {number} simValue, 相似度,默认为0。此处参数用作绿幕扣除微调RBG值。取值应当大于等于0 1350 | * @return {Promise.} 成功返回true,失败返回false 1351 | */ 1352 | public boolean makeMetahumanVideoClone(String saveVideoFolder, String text, String language, String bgFilePath, int simValue) { 1353 | return boolCmd("makeMetahumanVideoClone", saveVideoFolder, text, language, bgFilePath, Integer.toString(simValue)); 1354 | } 1355 | 1356 | /** 1357 | * 生成数字人短视频,此函数需要调用 initSpeechService 初始化语音服务 1358 | * 1359 | * @param {string} saveVideoFolder, 保存的视频目录 1360 | * @param {string} text,要转换语音的文本 1361 | * @param {string} language,语言,参考开发文档 语言和发音人 1362 | * @param {string} voiceName,发音人,参考开发文档 语言和发音人 1363 | * @param {string} bgFilePath,数字人背景 图片/视频 路径,扣除绿幕会自动获取绿幕的RGB值,null 则不替换背景。仅替换绿幕背景的数字人模型 1364 | * @param {number} simValue, 相似度,默认为0。此处参数用作绿幕扣除微调RBG值。取值应当大于等于0 1365 | * @param {string} voiceStyle,语音风格,默认General常规风格,其他风格参考开发文档 语言和发音人 1366 | * @param {number} quality,音质,0低品质 1中品质 2高品质, 默认为0低品质 1367 | * @param {number} speechRate, 语速,默认为0,取值范围 -100 至 200 1368 | * @return {Promise.} 成功返回true,失败返回false 1369 | */ 1370 | public boolean makeMetahumanVideo(String saveVideoFolder, String text, String language, String voiceName, String bgFilePath, int simValue, String voiceStyle, int quality, int speechRate) { 1371 | if (StringUtils.isBlank(voiceStyle)) { 1372 | voiceStyle = "General"; 1373 | } 1374 | return boolCmd("makeMetahumanVideo", saveVideoFolder, text, language, voiceName, bgFilePath, Integer.toString(simValue), voiceStyle, Integer.toString(quality), Integer.toString(speechRate)); 1375 | } 1376 | 1377 | /** 1378 | * 生成数字人说话文件,生成MP3文件和 lab文件,提供给 metahumanSpeechByFile 和使用 1379 | * 1380 | * @param {string} saveAudioPath, 保存的音频文件路径,扩展为.MP3格式。同名的 .lab文件需要和音频文件在同一目录下 1381 | * @param {string} text,要转换语音的文本 1382 | * @param {string} language,语言,参考开发文档 语言和发音人 1383 | * @param {string} voiceName,发音人,参考开发文档 语言和发音人 1384 | * @param {number} quality,音质,0低品质 1中品质 2高品质, 默认为0低品质 1385 | * @param {number} speechRate, 语速,默认为0,取值范围 -100 至 200 1386 | * @param {string} voiceStyle,语音风格,默认General常规风格,其他风格参考开发文档 语言和发音人 1387 | * @return {Promise.} 成功返回true,失败返回false 1388 | */ 1389 | public boolean makeMetahumanSpeechFile(String saveAudioPath, String text, String language, String voiceName, int quality, int speechRate, String voiceStyle) { 1390 | if (StringUtils.isBlank(voiceStyle)) { 1391 | voiceStyle = "General"; 1392 | } 1393 | return boolCmd("makeMetahumanVideo", saveAudioPath, text, language, voiceName, Integer.toString(quality), Integer.toString(speechRate), voiceStyle); 1394 | } 1395 | 1396 | /** 1397 | * 初始化数字人声音克隆服务 1398 | * 1399 | * @param {string} apiKey, API密钥 1400 | * @param {string} voiceId, 声音ID 1401 | * @return {Promise.} 成功返回true,失败返回false 1402 | */ 1403 | public boolean initSpeechCloneService(String apiKey, String voiceId) { 1404 | return boolCmd("initSpeechCloneService", apiKey, voiceId); 1405 | } 1406 | 1407 | /** 1408 | * 数字人使用克隆声音说话,此函数需要调用 initSpeechCloneService 初始化语音服务 1409 | * 1410 | * @param {string} saveAudioPath, 保存的发音文件路径。这里是路径,不是目录! 1411 | * @param {string} text,要转换语音的文本 1412 | * @param {string} language,语言,中文:zh-cn,其他语言:other-languages 1413 | * @param {boolean} waitPlaySound,等待音频播报完毕, true等待/false不等待 1414 | * @return {Promise.} 成功返回true,失败返回false 1415 | */ 1416 | public boolean metahumanSpeechClone(String saveAudioPath, String text, String language, boolean waitPlaySound) { 1417 | return boolCmd("metahumanSpeechClone", saveAudioPath, text, language, Boolean.toString(waitPlaySound)); 1418 | } 1419 | 1420 | /** 1421 | * 使用克隆声音生成数字人短视频,此函数需要调用 initSpeechCloneService 初始化语音服务 1422 | * 1423 | * @param {string} saveVideoFolder, 保存的视频和音频文件目录 1424 | * @param {string} text,要转换语音的文本 1425 | * @param {string} language,语言,中文:zh-cn,其他语言:other-languages 1426 | * @param {string} bgFilePath,数字人背景 图片/视频 路径,扣除绿幕会自动获取绿幕的RGB值,null 则不替换背景。仅替换绿幕背景的数字人模型 1427 | * @param {number} simValue, 相似度,默认为0。此处参数用作绿幕扣除微调RBG值。取值应当大于等于0 1428 | * @return {Promise.} 成功返回true,失败返回false 1429 | */ 1430 | public boolean makeMetahumanVideoClone(String saveVideoFolder, String text, String language, String bgFilePath, int simValue) { 1431 | return boolCmd("makeMetahumanVideoClone", saveVideoFolder, text, language, bgFilePath, Integer.toString(simValue)); 1432 | } 1433 | 1434 | /** 1435 | * 生成数字人说话文件(声音克隆),生成MP3文件和 lab文件,提供给 metahumanSpeechByFile 和使用 1436 | * 1437 | * @param {string} saveAudioPath, 保存的发音文件路径。这里是路径,不是目录! 1438 | * @param {string} text,要转换语音的文本 1439 | * @param {string} language,语言,中文:zh-cn,其他语言:other-languages 1440 | * @return {Promise.} 成功返回true,失败返回false 1441 | */ 1442 | public boolean makeMetahumanSpeechFileClone(String saveAudioPath, String text, String language) { 1443 | return boolCmd("makeMetahumanSpeechFileClone", saveAudioPath, text, language); 1444 | } 1445 | 1446 | /** 1447 | * 获取WindowsDriver.exe 命令扩展参数,一般用作脚本远程部署场景,WindowsDriver.exe驱动程序传递参数给脚本服务端 1448 | * 1449 | * @return {Promise.} 返回WindowsDriver驱动程序的命令行参数(不包含ip和port) 1450 | */ 1451 | public String getExtendParam() { 1452 | return strCmd("getExtendParam"); 1453 | } 1454 | 1455 | /** 1456 | * 获取Windows ID 1457 | * 1458 | * @return {Promise.} 成功返回Windows ID 1459 | */ 1460 | public String getWindowsId() { 1461 | return strCmd("getWindowsId"); 1462 | } 1463 | 1464 | /** 1465 | * 切换新的人物形象动作,此函数无需训练数字人模型,直接切换各种人物形象动作和场景。 1466 | * 1467 | * @param {string} callApiKey, 调用函数的密钥 1468 | * @param {string} actionVideoOrImage, 闭嘴的人物视频或者图片 1469 | * @return {Promise.} 成功返回true,失败返回false。调用不会立刻生效,加载完素材会自动切换 1470 | */ 1471 | public boolean switchAction(String callApiKey, String actionVideoOrImage) { 1472 | return boolCmd("switchAction", callApiKey, actionVideoOrImage); 1473 | } 1474 | 1475 | /** 1476 | * 训练数字人,训练时长为10-30分钟 1477 | * 1478 | * @param {string} callApiKey, 调用函数的密钥 1479 | * @param {string} trainVideoOrImagePath, 闭嘴的人物视频或者图片 素材 1480 | * @param {string} srcMetahumanModelPath, 预训练数字人模型路径 1481 | * @param {string} saveHumanModelFolder, 保存训练完成的模型目录 1482 | * @return {Promise.} 成功返回true,失败返回false 1483 | */ 1484 | public boolean trainHumanModel(String callApiKey, String trainVideoOrImagePath, String srcMetahumanModelPath, String saveHumanModelFolder) { 1485 | return boolCmd("trainHumanModel", callApiKey, trainVideoOrImagePath, srcMetahumanModelPath, saveHumanModelFolder); 1486 | } 1487 | 1488 | /** 1489 | * 切换声音克隆模型 1490 | * 1491 | * @param {string} cloneServerIp, 克隆声音服务端 1492 | * @param {string} gptWeightsPath, gpt 模型权重路径。指克隆服务所在的电脑/服务器 路径 1493 | * @param {string} sovitsWeightsPath, sovits 模型权重路径。指克隆服务所在的电脑/服务器 路径 1494 | * @return {Promise.} 失败返回false,成功返回true。 切换到与原模型无关音色的模型,切记更换参考音频和文本 1495 | */ 1496 | public boolean switchCloneAudioModel(String cloneServerIp, String gptWeightsPath, String sovitsWeightsPath) { 1497 | return this.boolCmd("switchCloneAudioModel", cloneServerIp, gptWeightsPath, sovitsWeightsPath); 1498 | } 1499 | 1500 | /** 1501 | * 重启声音克隆服务 1502 | * 1503 | * @param {string} cloneServerIp, 克隆声音服务端 1504 | * @return {Promise.} 失败返回false,成功返回true。重启服务会中断连接,实际并未准确返回值。重启后模型加载需要时间,调用此函数需显示等待几秒,再去访问声音克隆服务 1505 | */ 1506 | public boolean restartCloneAudioServer(String cloneServerIp) { 1507 | return this.boolCmd("restartCloneAudioServer", cloneServerIp); 1508 | } 1509 | 1510 | /** 1511 | * 克隆声音,需要部署服务端 1512 | * 1513 | * @param {string} cloneServerIp, 克隆声音服务端 1514 | * @param {string} saveAudioPath, 保存克隆声音的路径 1515 | * @param {string} referAudioPath, 参考音频路径,3-10秒,音频时长不能大于等于10秒 1516 | * @param {string} referText, 参考音频对应的文本 1517 | * @param {string} cloneText, 要克隆的文本 1518 | * @param {number} speedFactor, 语速(0.5为半速,1.0为正常速度,1.5为1.5倍速,以此类推)。默认为1.0 正常语速 1519 | * @return {Promise.} 失败返回false,成功返回true 1520 | */ 1521 | public boolean makeCloneAudio(String cloneServerIp, String saveAudioPath, String referAudioPath, String referText, String cloneText, float speedFactor) { 1522 | if (speedFactor <= 0) { 1523 | speedFactor = 1.0F; 1524 | } 1525 | return this.boolCmd("makeCloneAudio", cloneServerIp, saveAudioPath, referAudioPath, referText, cloneText, String.valueOf(speedFactor)); 1526 | } 1527 | 1528 | /** 1529 | * 播报音频文件 1530 | * 1531 | * @param {string} audioPath, 音频文件路径 1532 | * @param {boolean} isWait, 是否等待.为true时,等待播放完毕 1533 | * @return {Promise.} 失败返回false,成功返回true 1534 | */ 1535 | public boolean playAudio(String audioPath, boolean isWait) { 1536 | return this.boolCmd("playAudio", audioPath, Boolean.toString(isWait)); 1537 | } 1538 | 1539 | /** 1540 | * 播报音频文件(EX),playAudioEx 不能与 metahumanSpeechByFileEx 同步执行 1541 | * 1542 | * @param {string} audioPath, 音频文件路径 1543 | * @param {boolean} enableRandomParam, 是否启用随机去重参数 1544 | * @param {boolean} isWait, 是否等待.为true时,等待播放完毕 1545 | * @return {Promise.} 总是返回true,函数仅添加播放音频文件到队列不处理返回 1546 | */ 1547 | public boolean playAudioEx(String audioPath, boolean enableRandomParam, boolean isWait) { 1548 | return this.boolCmd("playAudioEx", audioPath, Boolean.toString(enableRandomParam), Boolean.toString(isWait)); 1549 | } 1550 | 1551 | /** 1552 | * 播报视频文件 1553 | * 1554 | * @param {string} videoPath, 视频文件路径 (多个视频切换播放 视频和音频编码必须一致) 1555 | * @param {number} videoSacle, 视频缩放(0.5缩小一半,1.0为原始大小) 1556 | * @param {boolean} isLoopPlay, 是否循环播放 1557 | * @param {boolean} enableRandomParam, 是否启用随机去重参数 1558 | * @param {boolean} isWait, 是否等待播报完毕。 值为false时,不等待播放结束。未播报结束前再次调用此函数 会终止前面的播报内容 1559 | * @return {Promise.} 失败返回false,成功返回true。 1560 | */ 1561 | public boolean playMedia(String videoPath, float videoSacle, boolean isLoopPlay, boolean enableRandomParam, boolean isWait) { 1562 | if (videoSacle <= 0) { 1563 | videoSacle = 1.0F; 1564 | } 1565 | return this.boolCmd("playMedia", videoPath, Float.toString(videoSacle), Boolean.toString(isLoopPlay), Boolean.toString(enableRandomParam), Boolean.toString(isWait)); 1566 | } 1567 | 1568 | /** 1569 | * 调节 playMedia 音量大小(底层用的内存共享,支持多进程控制) 1570 | * 1571 | * @param {number} volumeScale, 音量缩放(0.5调低一半,1.0为原始音量大小)。默认为原始大小 1572 | * @return {Promise.} 失败返回false,成功返回true。 1573 | */ 1574 | public boolean setMediaVolumeScale(float volumeScale) { 1575 | return this.boolCmd("setMediaVolumeScale", Float.toString(volumeScale)); 1576 | } 1577 | 1578 | /** 1579 | * 生成lab文件,需要部署服务端 1580 | * 1581 | * @param {string} labServerIp, lab服务端IP 1582 | * @param {string} audioPath, 音频文件 1583 | * @return {Promise.} 失败返回false,成功返回true 并生成 与 audioPath 同目录下的 .lab 后缀文件。(音频文件+lab文件可以直接驱动数字人) 1584 | */ 1585 | public boolean makeCloneLab(String labServerIp, String audioPath) { 1586 | return this.boolCmd("makeCloneLab", labServerIp, audioPath); 1587 | } 1588 | 1589 | /** 1590 | * 语音识别,需要部署服务端 1591 | * 1592 | * @param {string} labServerIp, lab服务端IP 1593 | * @param {string} audioPath, 音频文件 1594 | * @return {Promise.} 失败返回null, 成功返回识别到的内容 1595 | */ 1596 | public boolean cloneAudioToText(String labServerIp, String audioPath) { 1597 | return this.boolCmd("cloneAudioToText", labServerIp, audioPath); 1598 | } 1599 | 1600 | /** 1601 | * 关闭驱动 1602 | * 1603 | * @return boolean 1604 | */ 1605 | public boolean closeDriver() { 1606 | return boolCmd("closeDriver"); 1607 | } 1608 | 1609 | } 1610 | --------------------------------------------------------------------------------