diff --git a/app/build.gradle b/app/build.gradle index d71b6250..a875da02 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "com.stardust.scriptdroid" minSdkVersion 17 targetSdkVersion 23 - versionCode 147 - versionName "2.0.14 Beta" + versionCode 151 + versionName "2.0.14 Beta2.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true ndk { diff --git a/app/src/main/assets/help/catalogue.json b/app/src/main/assets/help/catalogue.json index 1f6fe58d..13871677 100644 --- a/app/src/main/assets/help/catalogue.json +++ b/app/src/main/assets/help/catalogue.json @@ -5,6 +5,11 @@ "type": "markdown", "path":"documentation" }, + { + "title": "如何阅读本文档", + "type": "markdown", + "path":"documentation" + }, { "title": "ECMAScript教程", "type": "catalogue" @@ -24,16 +29,6 @@ "type": "markdown", "path":"documentation" }, - { - "title": "控制台与输出", - "type": "markdown", - "path":"documentation" - }, - { - "title": "UI(用户界面)", - "type": "markdown", - "path":"documentation" - }, { "title": "选择器", "type": "markdown", @@ -45,7 +40,12 @@ "path": "documentation" }, { - "title": "模块与第三方jar", + "title": "图片与图色处理", + "type": "markdown", + "path":"documentation" + }, + { + "title": "控制台与输出", "type": "markdown", "path":"documentation" }, @@ -59,6 +59,26 @@ "type": "markdown", "path":"documentation" }, + { + "title": "UI(用户界面)", + "type": "markdown", + "path":"documentation" + }, + { + "title": "文件读写", + "type": "markdown", + "path":"documentation" + }, + { + "title": "应用", + "type": "markdown", + "path":"documentation" + }, + { + "title": "模块与第三方jar", + "type": "markdown", + "path":"documentation" + }, { "title": "调用Java API", "type": "markdown", diff --git a/app/src/main/assets/help/documentation/一般常用函数.md b/app/src/main/assets/help/documentation/一般常用函数.md index 0b963910..7198c4bd 100644 --- a/app/src/main/assets/help/documentation/一般常用函数.md +++ b/app/src/main/assets/help/documentation/一般常用函数.md @@ -41,16 +41,30 @@ launchApp("微信"); 设置剪贴板内容。此剪贴板即系统剪贴板,在一般应用的输入框中"粘贴"既可使用。 +### getClip() + +返回系统剪贴板的内容。 + +### waitForActivity(activity\[, period = 200\]) +* activity Activity名称 +* period 轮询等待间隔(毫秒) + +等待指定的Activity出现。 + + +### waitForPackage(package\[, period = 200\]) +* package 包名 +* period 轮询等待间隔(毫秒) + +等待指定的应用出现。例如`waitForPackage("com.tencent.mm")`为等待当前界面为微信。 + ### isStopped() 当脚本处于应当停止运行的状态时返回true, 否则返回false。 由于脚本引擎的关系,有时即使强制停止正在运行的脚本也不会生效,此时会出现isStopped()为true但脚本仍在运行的情况。 ### notStopped() 当脚本处于应当停止运行的状态时返回false, 否则返回false。 -为了避免脚本处于循环中无法正常结束,在诸如while(true)的循环中建议使用while(notStopped())代替,例如: -``` -while(notStopped()){ - //do something -} -``` + +### stop() +立即停止脚本运行。 diff --git a/app/src/main/assets/help/documentation/图片与图色处理.md b/app/src/main/assets/help/documentation/图片与图色处理.md index d2d88e3b..e370f366 100644 --- a/app/src/main/assets/help/documentation/图片与图色处理.md +++ b/app/src/main/assets/help/documentation/图片与图色处理.md @@ -1,5 +1,179 @@ +目录: +* [截图](#截图) +* [images](#images) +* [colors](#colors) +* [Image](#Image) +* [Point](#Point) + +# 截图 + +截图有关的函数需要安卓5.0以上才支持。 ### requestScreenCapture(\[width, height\]) +* width \ 截图宽度 +* height \ 截图高度 -参数width和height用于指定截图的分辨率,默认为屏幕宽高。 +向系统申请屏幕截图权限,返回是否请求成功。 -未完待续。 \ No newline at end of file +第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。 + +这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是[captureScreen](#captureScreen())。 + +不指定参数时默认为屏幕宽高。指定参数时,并不会严格以width和height为截图宽度和高度,而是以和width, height接近的最适合的宽高为截图宽高。例如在1920\*1080屏幕中requestScreenCapture(600, 1000)请求的截图的高度一般是540\*960。 + +如果在第一次权限请求时选择"总是允许", 之后的脚本执行该函数通常耗时200毫秒以内(测试机型:小米6)。 + +建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏。 + +### captureScreen() + +截取当前屏幕并返回一个[Image](#Image)对象。该图片的宽高取决于requestScreenCapture所指定的宽高。 + +没有截图权限时执行该函数会抛出SecurityException。 + +该函数耗时一毫秒以内(测试机型:小米6)。因此不必担心截图的速度。图色处理的瓶颈通常在找色部分。 + +### captureScreen(path) +* path \ 截图保存路径 + +截取当前屏幕并以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。 + +# images + +images是图片与图色处理的工具。包括获取图片某点颜色,保存图片,找色等。 + +### images.pixel(image, x, y) +* image \ 图片 +* x \ 要获取的像素的横坐标。坐标系以图片左上角为原点。 +* y \ 要获取的像素的纵坐标。 + +返回图片image在点(x, y)处的像素的ARGB值。 +该值的格式为0xAARRGGBB。也就是,如果`var argb = images.pixel(image, x, y)`,那么可以通过`(argb & 0xFF000000) >> 24`获取透明度,通过`(argb & 0xFF0000) >> 16`获取R值,通过`(argb & 0xFF000000) >> 24`获取G值等,或者通过`images.alpha(argb)`, `images.red(argb)`等获取。 + +### images.saveImage(image, path) + +* image \ 图片 +* path \ 路径 + +把图片image以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。 + +### images.findColor(image, color, options) +* image \ 图片 +* color \或\ 要寻找的颜色的RGB值。如果是一个整数,则以0xRRGGBB的形式代表RGB值(A通道会被忽略);如果是字符串,则以"#RRGGBB"代表其RGB值。 +* options \ 选项 + +在图片中寻找颜色color。找到时返回找到的点[Point](#Point),找不到时返回null。 +选项包括: +* region \ 找色区域。是一个两个或四个元素的数组。(region\[0\], region\[1\])表示找色区域的左上角;region\[2\]*region\[3\]表示找色区域的宽高。如果只有region只有两个元素,则找色区域为(region\[0\], region\[1\])到屏幕右下角。如果不指定region选项,则找色区域为整张图片。 +* threads \ 指定找色使用的线程数。不能超过16。最适合的线程数取决于设备。默认线程数为2。 +* algorithm \ 指定颜色匹配算法。包括: + * "equal": 相等匹配,只有与给定颜色color完全相等时才匹配。 + * "diff": 差值匹配。与给定颜色的R、G、B差的绝对值之和小于threshold时匹配。 + * "rgb": rgb欧拉距离相似度。与给定颜色color的rgb欧拉距离小于等于threshold时匹配。 + * "rgb+": 加权rgb欧拉距离匹配([LAB Delta E](https://en.wikipedia.org/wiki/Color_difference))。 + * "hs": hs欧拉距离匹配。hs为HSV空间的色调值。 +* threshold \ 找色时颜色相似度的临界值,范围为0~255(越小越相似,0为颜色相等,255为任何颜色都能匹配)。默认为16。threshold和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255. + +该函数也可以作为全局函数使用。 + + +### images.findColorInRegion(img, color, x, y\[, width, height, threads, algorithm, threshold\]) + +相当于 +``` +images.findColor(img, color, { + region: [x, y, width, height], + algorithm: algorithm, + threshold: threshold, + threads: threads +}); +``` + +该函数也可以作为全局函数使用。 + +### images.findColorEquals(img, color, x, y, width, height, threads) + +相当于 +``` +images.findColor(img, color, { + region: [x, y, width, height], + algorithm: "equal", + threads: threads +}); +``` + +该函数也可以作为全局函数使用。 + + +### images.detectsColor(image, color, x, y\[, threshold = 16, algorithm = "rgb"\]) +* image \ 图片 +* color \ 要检测的颜色 +* x \ 要检测的位置横坐标 +* y \ 要检测的位置纵坐标 +* threshold \ 颜色相似度临界值,默认为16 +* algorithm \ 颜色匹配算法,默认为rgb欧式距离 + +返回图片image在位置(x, y)处是否匹配到颜色color。有关threshold和algorithm的信息,参考findColor函数。 + +# colors + +colors是颜色处理的工具对象。包含一些常用方法,除了android.graphics.Color的所有方法外,加上toString方法。 + +### colors.toString(color) +* color \ 整数RGB颜色值 + +返回颜色值的字符串,格式为 #AARRGGBB。 + +### colors.red(color) +* color \ 整数RGB颜色值 + +返回颜色color的R通道的值,范围0~255. + +### colors.green(color) +* color \ 整数RGB颜色值 + +返回颜色color的G通道的值,范围0~255. + +### colors.blue(color) +* color \ 整数RGB颜色值 + +返回颜色color的B通道的值,范围0~255. + +### colors.alpha(color) +* color \ 整数RGB颜色值 + +返回颜色color的Alpha通道的值,范围0~255. + +### colors.rgb(red, green, blue) +* red \ 颜色的R通道的值 +* blue \ 颜色的G通道的值 +* green \ 颜色的B通道的值 + +返回这些颜色通道构成的整数颜色值。Alpha通道将是255(不透明)。 + +### colors.argb(alpha, red, green, blue) +* alpha \ 颜色的Alpha通道的值 +* red \ 颜色的R通道的值 +* green \ 颜色的G通道的值 +* blue \ 颜色的B通道的值 + +返回这些颜色通道构成的整数颜色值。 + +# Image + +[captureScreen](#captureScreen()) 返回的对象。 + +### Image.getWidth() + +返回以像素为单位图片宽度。 + +### Image.getHeight() + +返回以像素为单位的图片高度。 + +# Point + +findColor返回的对象。 + +### Point.x +### Point.y + 坐标。 diff --git a/app/src/main/assets/help/documentation/如何阅读本文档.md b/app/src/main/assets/help/documentation/如何阅读本文档.md new file mode 100644 index 00000000..70b96c0a --- /dev/null +++ b/app/src/main/assets/help/documentation/如何阅读本文档.md @@ -0,0 +1,30 @@ +先看一个例子,下面是《自动操作函数》的章节中input函数的部分说明。 +### input(\[i, \]text) +* i \ 表示要输入的为第i + 1个输入框 +* text \ 要输入的文本 + +input表示函数名,括号内为函数的参数。下面是参数列表,"\"表示该参数类型为数值,"\"表示该参数类型为字符串。 + +例如`input(1, "啦啦啦")`,执行这个语句会在屏幕上的第2个输入框处输入"啦啦啦“。 + +方括号\[ \]表示参数为可选参数。也就是说,可以省略i直接调用input。例如`input("嘿嘿嘿")`,按照文档,这个语句会在屏幕上所有输入框输入"嘿嘿嘿"。 + +调用有可选参数的函数时请**不要**写上方括号。 + +我们再看第二个例子。图片和图色处理中detectsColor函数的部分说明。 + +### images.detectsColor(image, color, x, y\[, threshold = 16, algorithm = "rgb"\]) +* image \ 图片 +* color \ 要检测的颜色 +* x \ 要检测的位置横坐标 +* y \ 要检测的位置纵坐标 +* threshold \ 颜色相似度临界值,默认为16 +* algorithm \ 颜色匹配算法,默认为rgb欧式距离 + +同样地,"\[, threshold = 16, algorithm = "rgb"\]"为可选参数,并且,等于号=后面的值为参数的默认值。也就是如果不指定该参数,则该参数将会为这个值。 + +例如 `images.detectsColor(captureScreen(), "#112233", 100, 200)` 相当于 `images.detectsColor(captureScreen(), "#112233", 100, 200, 16, "rgb")`,而`images.detectsColor(captureScreen(), "#112233", 100, 200, 64)` 相当于`images.detectsColor(captureScreen(), "#112233", 100, 200, 64, "rgb")` 。 + +调用有可选参数及默认值的函数时请**不要**写上方括号和等于号。 + +好了先这样吧 :-\) diff --git a/app/src/main/assets/help/documentation/应用.md b/app/src/main/assets/help/documentation/应用.md new file mode 100644 index 00000000..651d6703 --- /dev/null +++ b/app/src/main/assets/help/documentation/应用.md @@ -0,0 +1,46 @@ + +### app.intent(intent) +* intent \ 一个表示Intent对象,其属性可以包括: + * action \ 这个Intent的Action,比如"android.intent.action.SEND" + * type \ 这个Intent的MimeType,比如"text/plain" + * data \ 这个Intent的Data(Uri),可以是文件路径或者Url等。 + * category \ 这个Intent的Category的字符串数组。 + * packageName \ 目标包名 + * className \ 目标Activity或Service等组件的名称 + * extras \ 以键值对构成的这个Intent的Extras。 + +返回用intent对象构造的android.content.Intent对象。 + +如果你看了一脸懵逼,请百度[安卓Intent](https://www.baidu.com/s?wd=android%20Intent)。 + +### app.startActivity(intent) +* intent \ 与app.intent函数的参数一样的intent对象。 + +相当于context.startActivity(intent)。 + + +### app.sendBroadcast(intent) +* intent \ 与app.intent函数的参数一样的intent对象。 + +相当于context.sendBroadcast(intent)。 + +### app.viewFile(path) +* path \ 文件路径 + +用其他应用查看文件。 + +### app.editFile(path) +* path \ 文件路径 + +用其他应用编辑文件。 + +### app.uninstall(packageName) +* packageName \ 应用包名 + +卸载应用。 + +### app.openUrl(url) +* url \ 网站的Url + +用浏览器打开网站url。 + diff --git a/app/src/main/assets/help/documentation/控制台与输出.md b/app/src/main/assets/help/documentation/控制台与输出.md index 0754e680..515a6bf7 100644 --- a/app/src/main/assets/help/documentation/控制台与输出.md +++ b/app/src/main/assets/help/documentation/控制台与输出.md @@ -1,18 +1,27 @@ 控制台通常用来输出一些调试信息和运算结果。 ### openConsole() +### console.show() + 显示控制台。 ### clearConsole() +### console.clear() + 清空控制台。 + ### log(text) +### console.log(text) + * text \ | \ 要打印到控制台的信息 -### print(text) -等同于log。 +在控制台中输出文本text并换行,例如`log("Hello world");`。当text是一个对象时则会转换String以后再输出。 -在控制台中输出日志,例如`log("Hello world");`。当text是一个对象时则会转换String以后再输出。 +### print(text) + +在控制台中输出文本text。 ### err(error) +### console.error(error) * error \ | \ 在控制台中输出错误信息,以红色字体显示,例如: @@ -29,4 +38,19 @@ try{ ### toast(message) * message \ | \ 要显示的信息 -以气泡显示信息message几秒。(具体时间取决于安卓系统) \ No newline at end of file +以气泡显示信息message几秒。(具体时间取决于安卓系统) + +### toastLog(message) +* message \ | \ 要显示的信息 + +相当于`toast(message);log(message)`。 + +### console.assert(value, message) +* value \ 要断言的布尔值 +* message \ value为false时要输出的信息 + +断言。如果value为false则输出message并停止脚本运行。 + +### console.input(data\[, ...args\]) + +### console.rawInput(data\[, ...args\]) diff --git a/app/src/main/assets/help/documentation/文件读写.md b/app/src/main/assets/help/documentation/文件读写.md new file mode 100644 index 00000000..052c9177 --- /dev/null +++ b/app/src/main/assets/help/documentation/文件读写.md @@ -0,0 +1,196 @@ + +### open(path\[, mode = "r", encoding = "utf-8", bufferSize = 8192\]) +* path \ 文件路径,例如"/sdcard/1.txt"。 +* mode \ 文件打开模式,包括: + * "r": 只读模式。该模式下只能对文件执行**文本**读取操作。 + * "w": 只写模式。该模式下只能对文件执行**文本**覆盖写入操作。 + * "a": 附加模式。该模式下将会把写入的文本附加到文件末尾。 + 目前暂不支持二进制模式,随机读写模式。 +* encoding \ 字符编码。 +* bufferSize \ 文件读写的缓冲区大小。 + +打开一个文件。根据打开模式返回不同的文件对象。包括: +* "r": 返回一个ReadableTextFile对象。 +* "w", "a": 返回一个WritableTextFile对象。 + +对于"w"模式,如果文件并不存在,则会创建一个,已存在则会清空该文件内容;其他模式文件不存在会抛出FileNotFoundException。 + +# files + +文件处理的工具对象。 + +### files.isFile(path) +* path \ 路径 + +返回路径path是否是文件。 + +### files.isDir(path) +* path \ 路径 + +返回路径path是否是文件夹。 + +### files.isDirEmpty(path) +* path \ 路径 + +返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回false。 + +### files.join(parent, child) +* parent \ 父目录路径 +* child \ 子路径 + +连接两个路径并返回,例如`files.join("/sdcard/", "1.txt")`返回"/sdcard/1.txt"。 + +### files.create(path) +* path \ 路径 + +创建一个文件并返回是否创建成功。 + +### files.createIfNotExists(path) +* path \ 路径 + +创建一个文件并返回是否创建成功。 + +### files.exists(path) +* path \ 路径 + +返回在路径path处的文件是否存在。 + +### files.ensureDir(path) +* path \ 路径 + +确保路径path所在的文件夹存在。 + +例如对于路径"/sdcard/Download/ABC/1.txt",如果/Download/文件夹不存在,则会先创建Download,再创建ABC文件夹。 + +### files.read(path\[, encoding = "utf-8"\]) +* path \ 路径 +* encoding \ 字符编码 + +读取文件path的所有内容并返回。 + +### files.write(path, text\[, encoding = "utf-8"\]) +* path \ 路径 +* text \ 要写入的文本内容 +* encoding \ 字符编码 + +把text写入到文件path中。如果文件存在则覆盖,不存在则创建。 + +### files.copy(fromPath, toPath) +* fromPath \ 要复制的原文件路径 +* toPath \ 复制到的文件路径 + +复制文件。例如`files.copy("/sdcard/1.txt", "/sdcard/Download/1.txt")`。 + +### files.rename(path, newName) +* path \ 要重命名的原文件路径 +* newName \ 要重命名的新文件名 + +重命名文件,并返回是否重命名成功。例如`files.rename("/sdcard/1.txt", "2.txt")`。 + +也可以用作文件移动,例如`files.rename("/sdcard/1.txt", "/sdcard/Download/1.txt")`会把1.txt文件从sd卡根目录移动到Download文件夹。 + +### files.renameWithoutExtension(path, newName) +* path \ 要重命名的原文件路径 +* newName \ 要重命名的新文件名 + +重命名文件,不包含拓展名,并返回是否重命名成功。例如`files.rename("/sdcard/1.txt", "2")`会把1.txt重命名为2.txt。 + +### files.getName(path) +* path \ 路径 + +返回文件的文件名。例如`files.getName("/sdcard/1.txt")`返回"1.txt"。 + +### files.getNameWithoutExtension(path) +* path \ 路径 + +返回不含拓展名的文件的文件名。例如`files.getName("/sdcard/1.txt")`返回"1"。 + +### files.getExtension(path) +* path \ 路径 + +返回文件的拓展名。例如`files.getExtension("/sdcard/1.txt")`返回"txt"。 + +### files.remove(path) +* path \ 路径 + +删除文件或**空文件夹**,返回是否删除成功。 + +### files.removeDir(path) +* path \ 路径 +* path \ 路径 + +删除文件夹,如果文件夹不为空,则删除该文件夹的所有内容再删除该文件夹,返回是否全部删除成功。 + +### files.getSdcardPath() + +返回SD卡路径。所谓SD卡,即外部存储器。 + +### files.listDir(path\[, filter\]) +* path \ 路径 +* filter \ 过滤函数。接收一个String参数(文件名),返回一个Boolean值。 + +列出文件夹path下的满足条件的文件和文件夹的名称的数组。如果不加filter参数,则返回所有文件和文件夹。 + +例如,获取sdcard目录下的txt文件为 +``` +var txtFiles = files.listDir("/sdcard/", function(name){ + return name.endsWith(".txt") && files.isFile("/sdcard/" + name); +}); +``` + +# ReadableTextFile + +可读文件对象。 + +### ReadableTextFile.read() + +返回该文件剩余的所有内容的字符串。 + +### ReadableTextFile.read(maxCount) + +* maxCount \ 最大读取的字符数量 + +读取该文件接下来最长为maxCount的字符串并返回。即使文件剩余内容不足maxCount也不会出错。 + +### ReadableTextFile.readline() + +读取一行并返回(不包含换行符)。 + +### ReadableTextFile.readlines() + +读取剩余的所有行,并返回它们按顺序组成的字符串数组。 + +### close() + +关闭该文件。 + +**打开一个文件不再使用时务必关闭** + +# PWritableTextFile + +可写文件对象。 + +### PWritableTextFile.write(text) +* text \ 文本 + +把文本内容text写入到文件中。 + +### PWritableTextFile.writeline(line) +* text \ 文本 + +把文本line写入到文件中并写入一个换行符。 + +### PWritableTextFile.writelines(lines) +* lines \ 字符串数组 + +把很多行写入到文件中.... + +### PWritableTextFile.flush() + +把缓冲区内容输出到文件中。 + +### PWritableTextFile.close() + +关闭文件。同时会被缓冲区内容输出到文件。 + +**打开一个文件写入后,不再使用时务必关闭,否则文件可能会丢失** diff --git a/app/src/main/assets/help/documentation/自动操作函数.md b/app/src/main/assets/help/documentation/自动操作函数.md index 3df99541..19da696b 100644 --- a/app/src/main/assets/help/documentation/自动操作函数.md +++ b/app/src/main/assets/help/documentation/自动操作函数.md @@ -27,6 +27,8 @@ while(!click("扫一扫")); * bottom 要点击的长方形区域下边与屏幕上边的像素距离 * right 要点击的长方形区域右边与屏幕左边的像素距离 +**注意,该函数一般只用于录制的脚本中使用,在自己写的代码中使用该函数一般没有作用。** + 当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true。 有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text[, i])`来点击,只能通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。 至于要定位点击的区域,可以在侧拉菜单开启"点击区域辅助"(或者安卓7.0以上在通知栏点击"修改"添加点击区域辅助快捷设定图标),之后每次点击或长按都会提示这次点击或长按的区域并自动保存,可以在编辑器的右侧拉菜单中插入。 @@ -43,13 +45,21 @@ while(!click("扫一扫")); 参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0);`。 ### scrollDown 下滑。不加参数时与scrollUp类似。 -### input([i, ]text) +### input(\[i, \]text) +* i \ 表示要输入的为第i + 1个输入框 +* text \ 要输入的文本 + +返回是否输入成功。当找不到对应的文本框时返回false。 +不加参数i则会在所有输入框都输入text。例如`input("测试")`。 + +### setText(\[i, \]text) * i \ 表示要输入的为第i + 1个输入框 * text \ 要输入的文本 返回是否输入成功。当找不到对应的文本框时返回false。 -这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。 不加参数i则会把所有输入框的文本都置为text。例如`input("测试")`。 +这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。 + ### back() 模拟按下返回键 diff --git a/app/src/main/assets/sample/QQ与微信/QQ加群成员脚本.js b/app/src/main/assets/sample/QQ与微信/QQ加群成员脚本.js index c9844a72..344fb9fb 100644 --- a/app/src/main/assets/sample/QQ与微信/QQ加群成员脚本.js +++ b/app/src/main/assets/sample/QQ与微信/QQ加群成员脚本.js @@ -3,44 +3,52 @@ var 好友验证信息 = "AutoJs自动添加群好友"; var 延迟 = 500; -toast("请打开群成员列表"); +launchApp("QQ"); +sleep(500); +if(currentActivity() != "com.tencent.mobileqq.activity.TroopMemberListActivity"){ + toast("请打开要加的群的聊天窗口"); + openGroupMemberList(); +} var added = {}; while(true){ - var list = className("AbsListView").findOne(); - list.children().each(function(child){ - if(child.className() != "android.widget.FrameLayout"){ - return; - } - if(!isGroupMember(child)){ - return; - } - if(isMyself(child)){ - return; - } - child.child(0).click(); - sleep(500); - addAsFriend(); - sleep(延迟); - }); - className("AbsListView").findOne().scrollForward(); + var list = className("AbsListView").findOne(); + var count = list.childCount(); + for(var i = 0; i < count; i++){ + var child = list.child(i); + if(!child || child.className() != "android.widget.FrameLayout"){ + continue; + } + if(!isGroupMember(child) || isMyself(child)){ + continue; + } + child.child(0).click(); + sleep(500); + addAsFriend(); + sleep(延迟); + } } - function isGroupMember(child){ - if(child.childCount() != 1){ - return false; - } - return child.child(0) && child.child(0).className() == "android.widget.FrameLayout"; + var tvName = child.findOne(id("tv_name")); + if(!tvName){ + return false; + } + log(tvName.text()); + return tvName.text() != "Baby Q"; } function isMyself(child){ - var l = child.findByText("我"); - return l && l.size() > 0; + var i = child.findOne(text("我")); + if(!i){ + return false; + } + return i.id() && !i.id().endsWith("tv_name"); } function addAsFriend(){ var qq = getQQ(); + toast(qq); if(added[qq]){ while(!click("返回")); return; @@ -54,13 +62,21 @@ function addAsFriend(){ if(click("取消")){ sleep(400); } - back(); + while(!back()); }else{ - back(); + while(!back()); } } function getQQ(){ var qq = textMatches("\\d{5,12}").findOne().text(); return qq; +} + +function openGroupMemberList(){ + desc("群资料卡").click(); + var groupMemberCountView = textEndsWith("名成员").findOne(); + var groupMemberCount = parseInt(/\d+/.exec(groupMemberCountView.text())[0]); + groupMemberCountView.parent().click(); + sleep(groupMemberCount * 4); } \ No newline at end of file diff --git a/app/src/main/assets/sample/QQ与微信/QQ名片点赞脚本.js b/app/src/main/assets/sample/QQ与微信/QQ名片点赞脚本.js index e5c8538b..0099f2bc 100644 --- a/app/src/main/assets/sample/QQ与微信/QQ名片点赞脚本.js +++ b/app/src/main/assets/sample/QQ与微信/QQ名片点赞脚本.js @@ -5,12 +5,7 @@ function 下滑(){ } function 赞(){ - var like = className("ImageView").desc("赞").find(); - if(like){ - like.click(); - return true; - } - return false; + className("ImageView").desc("赞").click(); } function 显示更多(){ @@ -21,13 +16,10 @@ function 显示更多(){ toast("请打开自己的资料页,点击点赞图标"); sleep(100); -waitForActivity("com.tencent.mobileqq.activity.VisitorsActivity"); while(notStopped()){ - var i = 0; - while(i < 10){ - i += 赞() ? 1 : 0; - click("取消"); + for(let i = 0; i < 10; i++){ + 赞(); } 显示更多(); 下滑(); diff --git a/app/src/main/assets/sample/QQ与微信/QQ名片点赞(只点赞好友).js b/app/src/main/assets/sample/QQ与微信/QQ名片点赞(只点赞好友).js new file mode 100644 index 00000000..fab8b21a --- /dev/null +++ b/app/src/main/assets/sample/QQ与微信/QQ名片点赞(只点赞好友).js @@ -0,0 +1,29 @@ +"auto"; + +var 延迟 = 100; + +launchApp("QQ"); +toast("请打开自己的资料页,点击点赞图标"); +sleep(500); +waitForActivity("com.tencent.mobileqq.activity.VisitorsActivity"); + +while(notStopped()){ + var list = className("AbsListView").findOne(); + list.children().each(function(child){ + if(!child) + return; + var l = child.findByText("(好友)"); + if(l.size() > 0){ + var like = child.findOne(className("ImageView").desc("赞")); + for(let i = 0; i < 10; i++){ + like.click(); + sleep(延迟); + } + } + }); + click("显示更多"); + click("显示更多"); + if(currentActivity() == "com.tencent.mobileqq.activity.VisitorsActivity"){ + list.scrollForward(); + } +} \ No newline at end of file diff --git a/app/src/main/assets/sample/QQ与微信/QQ批量同意好友验证.js b/app/src/main/assets/sample/QQ与微信/QQ批量同意好友验证.js new file mode 100644 index 00000000..d79a2c02 --- /dev/null +++ b/app/src/main/assets/sample/QQ与微信/QQ批量同意好友验证.js @@ -0,0 +1,11 @@ +"auto"; + +launchApp("QQ"); +toast("请打开新朋友界面并自行下滑"); + +while(true){ + text("同意").clickable().findOne().click(); + sleep(1500); + click("完成"); + sleep(1000); +} \ No newline at end of file diff --git a/app/src/main/assets/sample/QQ与微信/QQ点赞群成员.js b/app/src/main/assets/sample/QQ与微信/QQ点赞群成员.js index 073d1717..59dd90f6 100644 --- a/app/src/main/assets/sample/QQ与微信/QQ点赞群成员.js +++ b/app/src/main/assets/sample/QQ与微信/QQ点赞群成员.js @@ -1,45 +1,53 @@ "auto"; -var liked = {}; + +launchApp("QQ"); +sleep(500); +if(currentActivity() != "com.tencent.mobileqq.activity.TroopMemberListActivity"){ + toast("请打开要点赞的群的聊天窗口"); + openGroupMemberList(); +} + while(true){ var list = className("AbsListView").findOne(); - list.children().each(function(child){ - if(child.className() != "android.widget.FrameLayout"){ - return; - } - if(!isGroupMember(child)){ - return; - } - if(isMyself(child)){ - return; - } - child.child(0).click(); - like(); - while(!click("返回")); - while(!click("成员资料")); - while(!click("返回")); - }); - className("AbsListView").findOne().scrollForward(); + var count = list.childCount(); + for(var i = 0; i < count; i++){ + var child = list.child(i); + if(!child || child.className() != "android.widget.FrameLayout"){ + continue; + } + if(!isGroupMember(child) || isMyself(child)){ + continue; + } + child.child(0).click(); + sleep(500); + like(); + while(!click("返回")); + while(!click("成员资料")); + while(!click("返回")); + sleep(500); + } + list.scrollForward(); } function isGroupMember(child){ - if(child.childCount() != 1){ - return false; - } - return child.child(0) && child.child(0).className() == "android.widget.FrameLayout"; + var tvName = child.findOne(id("tv_name")); + if(!tvName){ + return false; + } + log(tvName.text()); + return tvName.text() != "Baby Q"; } function isMyself(child){ - var l = child.findByText("我"); - return l && l.size() > 0; + var i = child.findOne(text("我")); + if(!i){ + return false; + } + return i.id() && !i.id().endsWith("tv_name"); } function like(){ - var qq = getQQ(); - if(liked[qq]){ - while(!click("返回")); - return; - } while(!click("更多")); while(!click("查看个人资料卡")); var likeBtn = descEndsWith("点击可赞").findOne(); @@ -52,4 +60,12 @@ function like(){ function getQQ(){ var qq = textMatches("\\d{5,12}").findOne().text(); return qq; +} + +function openGroupMemberList(){ + desc("群资料卡").click(); + var groupMemberCountView = textEndsWith("名成员").findOne(); + var groupMemberCount = parseInt(/\d+/.exec(groupMemberCountView.text())[0]); + groupMemberCountView.parent().click(); + sleep(groupMemberCount * 4); } \ No newline at end of file diff --git a/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js b/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js index bbaaaa40..f19a9e2c 100644 --- a/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js +++ b/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js @@ -7,6 +7,10 @@ var y = 180; //获取在点(x, y)处的颜色 var c = images.pixel(captureScreen(), x, y); //显示该颜色 -toast((c >>> 0).toString(16)); +var msg = ""; +msg += "在位置(" + x + ", " + y + ")处的颜色为" + colors.toString(c); +msg += "\nR = " + colors.red(c) + ", G = " + colors.green(c) + ", B = " + colors.blue(c); //检测在点(x, y)处是否有颜色0x73bdb6 (模糊比较) -toast(images.detectsColor(captureScreen(), x, y, 0x73bdb6)) \ No newline at end of file +var isDetected = images.detectsColor(captureScreen(), "#73bdb6", x, y); +msg += "\n该位置是否匹配到颜色#73bdb6: " + isDetected; +alert(msg); \ No newline at end of file diff --git a/app/src/main/assets/sample/文件读写/删除所有空文件夹.js b/app/src/main/assets/sample/文件读写/删除所有空文件夹.js new file mode 100644 index 00000000..8bb9eef7 --- /dev/null +++ b/app/src/main/assets/sample/文件读写/删除所有空文件夹.js @@ -0,0 +1,20 @@ +if(confirm("该操作会删除SD卡目录及其子目录下所有空文件夹,是否继续?")){ + toast("请点击右上角打开日志"); + deleteAllEmptyDirs(files.getSdcardPath()); + toast("全部完成!"); +} + +function deleteAllEmptyDirs(dir){ + var list = files.listDir(dir); + var len = list.length; + if(len == 0){ + log("删除目录 " + dir + " " + (files.remove(dir) ? "成功" : "失败")); + return; + } + for(let i = 0; i < len; i++){ + var child = files.join(dir, list[i]); + if(files.isDir(child)){ + deleteAllEmptyDirs(child); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/Pref.java b/app/src/main/java/com/stardust/scriptdroid/Pref.java index e9d77967..e1fd28ca 100644 --- a/app/src/main/java/com/stardust/scriptdroid/Pref.java +++ b/app/src/main/java/com/stardust/scriptdroid/Pref.java @@ -143,6 +143,8 @@ public class Pref { } def().edit().putLong(KEY_LAST_SHOW_AD_MILLIS, System.currentTimeMillis()).apply(); return true; + case "Off": + return false; } return true; } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/HoverMenuManger.java b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/HoverMenuManger.java index 6d1bcf54..ccf24900 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/HoverMenuManger.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/HoverMenuManger.java @@ -30,7 +30,12 @@ public class HoverMenuManger { if (!HoverMenuService.isServiceRunning()) { if (!SettingsCompat.canDrawOverlays(App.getApp())) { Toast.makeText(App.getApp(), R.string.text_no_floating_window_permission, Toast.LENGTH_SHORT).show(); - SettingsCompat.manageDrawOverlays(App.getApp()); + try { + SettingsCompat.manageDrawOverlays(App.getApp()); + } catch (Exception e) { + e.printStackTrace(); + IntentUtil.goToAppDetailSettings(App.getApp()); + } } else { HoverMenuService.startService(App.getApp()); } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/HoverMenuService.java b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/HoverMenuService.java index aa265fcb..8a52e75f 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/HoverMenuService.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/HoverMenuService.java @@ -22,6 +22,7 @@ import com.stardust.scriptdroid.external.floatingwindow.menu.view.FloatingLayout import com.stardust.scriptdroid.external.floatingwindow.menu.view.FloatingLayoutHierarchyView; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.theme.ThemeColorManagerCompat; +import com.stardust.util.IntentUtil; import com.stardust.util.MessageEvent; import com.stardust.util.MessageIntent; @@ -129,7 +130,12 @@ public class HoverMenuService extends Service { } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, R.string.text_no_floating_window_permission, Toast.LENGTH_SHORT).show(); - SettingsCompat.manageDrawOverlays(this); + try { + SettingsCompat.manageDrawOverlays(this); + } catch (Exception ex) { + ex.printStackTrace(); + IntentUtil.goToAppDetailSettings(this); + } } } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/content/MainMenuNavigatorContent.java b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/content/MainMenuNavigatorContent.java index f688682d..dc6ed7c0 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/content/MainMenuNavigatorContent.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/content/MainMenuNavigatorContent.java @@ -17,6 +17,7 @@ import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.scriptdroid.ui.main.MainActivity_; import com.stardust.util.ClipboardUtil; import com.stardust.util.MessageEvent; +import com.stardust.view.accessibility.AccessibilityService; import org.greenrobot.eventbus.Subscribe; @@ -61,10 +62,14 @@ public class MainMenuNavigatorContent implements NavigatorContent { Toast.makeText(mView.getContext(), R.string.text_layout_inspector_is_dumping, Toast.LENGTH_SHORT).show(); return false; } - if (inspector.getCapture() == null) { + if (AccessibilityService.getInstance() == null) { Toast.makeText(mView.getContext(), R.string.text_no_accessibility_permission_to_capture, Toast.LENGTH_SHORT).show(); return false; } + if (inspector.getCapture() == null) { + Toast.makeText(mView.getContext(), R.string.text_inspect_failed, Toast.LENGTH_SHORT).show(); + return false; + } return true; } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/layout_inspector/LayoutInspector.java b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/layout_inspector/LayoutInspector.java index e2a30562..d81f4470 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/layout_inspector/LayoutInspector.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/layout_inspector/LayoutInspector.java @@ -1,5 +1,7 @@ package com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector; +import android.os.Build; +import android.support.annotation.RequiresApi; import android.util.Log; import android.view.accessibility.AccessibilityNodeInfo; @@ -43,6 +45,17 @@ public class LayoutInspector { } } + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + private void refreshChildList(AccessibilityNodeInfo root) { + if (root == null) + return; + root.refresh(); + int childCount = root.getChildCount(); + for (int i = 0; i < childCount; i++) { + refreshChildList(root.getChild(i)); + } + } + public boolean isDumping() { return mDumping; } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java index 58e1c6b4..c9be24bc 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java @@ -1,5 +1,6 @@ package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent; +import android.media.Image; import android.support.annotation.NonNull; import java.util.regex.Matcher; @@ -33,7 +34,7 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { if (mLastEventTime == 0) { mLastEventTime = event.time; } else if (event.time - mLastEventTime > 0.03) { - mCode.append("sleep(").append((long) (1000 * (event.time - mLastEventTime))).append(");\n"); + mCode.append("sh.usleep(").append((long) (1000000 * (event.time - mLastEventTime))).append(");\n"); mLastEventTime = event.time; } int device = parseDeviceNumber(event.device); @@ -122,11 +123,4 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { mCode.append("sh.exitAndWaitFor();"); } - private static String hex2dec(String hex) { - try { - return String.valueOf((int) Long.parseLong(hex, 16)); - } catch (NumberFormatException e) { - throw new EventFormatException(e); - } - } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java index aef226f0..e21ad312 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java @@ -15,6 +15,7 @@ import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.app.Fragment; import com.stardust.app.OperationDialogBuilder; +import com.stardust.pio.UncheckedIOException; import com.stardust.scriptdroid.script.ScriptFile; import com.stardust.pio.PFile; import com.stardust.scriptdroid.R; @@ -137,7 +138,9 @@ public class MyScriptListFragment extends Fragment { public void createScriptFile(String path, String script) { if (PFile.createIfNotExists(path)) { if (script != null) { - if (!PFile.write(path, script)) { + try { + PFile.write(path, script); + } catch (UncheckedIOException e) { Snackbar.make(getView(), R.string.text_file_write_fail, Snackbar.LENGTH_LONG).show(); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java index bd86ad45..69498280 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java @@ -140,7 +140,7 @@ public class SettingsActivity extends BaseActivity { .entry(getString(R.string.text_join_qq_group), new Runnable() { @Override public void run() { - if (!IntentUtil.joinQQGroup(getActivity(), "vjHXzZlpGcXNe-YEWzQ85mm_z8y-curC")) { + if (!IntentUtil.joinQQGroup(getActivity(), "-7riBQuwFUUqdgYL5vFeIdBfH4H9m-Uj")) { Toast.makeText(getActivity(), R.string.text_mobile_qq_not_installed, Toast.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/com/stardust/theme/ThemeColorManagerCompat.java b/app/src/main/java/com/stardust/theme/ThemeColorManagerCompat.java index 0c23a47e..22fdaf75 100644 --- a/app/src/main/java/com/stardust/theme/ThemeColorManagerCompat.java +++ b/app/src/main/java/com/stardust/theme/ThemeColorManagerCompat.java @@ -1,5 +1,7 @@ package com.stardust.theme; +import com.jecelyin.editor.v2.core.widget.JecEditText; +import com.jecelyin.editor.v2.ui.EditorDelegate; import com.stardust.scriptdroid.*; import com.stardust.scriptdroid.R; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69df5dc9..1c8dfea1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,7 +79,7 @@ 录制结束 复制到剪贴板 文件写入失败 - 加入QQ交流群 + 加入QQ互赞&交流群 已复制到剪贴板 使用音量键控制 开启后每次音量变化会开始或停止脚本录制 @@ -207,6 +207,7 @@ 格式错误 打开无障碍服务 放弃录制 + 布局抓取失败,请关闭悬浮窗后动一下页面重试 @@ -225,11 +226,13 @@ 默认 每天显示一次 + 关闭广告 Default OncePerDay + Off diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index d5048cf1..a6f91ed3 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -81,6 +81,8 @@ + + diff --git a/autojs/src/main/assets/javascript_engine_init.js b/autojs/src/main/assets/javascript_engine_init.js index acb0ee54..57803c14 100644 --- a/autojs/src/main/assets/javascript_engine_init.js +++ b/autojs/src/main/assets/javascript_engine_init.js @@ -36,3 +36,4 @@ require("__general__")(__runtime__, this); })(__that__); __importClass__(com.stardust.autojs.runtime.api.Shell); +__importClass__(com.stardust.autojs.runtime.api.InputEventSender); diff --git a/autojs/src/main/assets/modules/__general__.js b/autojs/src/main/assets/modules/__general__.js index 8b652d48..07c3523d 100644 --- a/autojs/src/main/assets/modules/__general__.js +++ b/autojs/src/main/assets/modules/__general__.js @@ -48,5 +48,12 @@ module.exports = function(__runtime__, scope){ } } + scope.waitForPackage = function(packageName, delay){ + delay = delay || 200; + while(scope.currentPackage() != packageName){ + sleep(delay); + } + } + scope.setScreenMetrics = __runtime__.setScreenMetrics.bind(__runtime__); } \ No newline at end of file diff --git a/autojs/src/main/assets/modules/__images__.js b/autojs/src/main/assets/modules/__images__.js index e21ab0f1..d957b25c 100644 --- a/autojs/src/main/assets/modules/__images__.js +++ b/autojs/src/main/assets/modules/__images__.js @@ -1,6 +1,10 @@ module.exports = function(__runtime__, scope){ var images = {}; + var colors = Object.create(android.graphics.Color); + colors.toString = function(color){ + return '#' + (color >>> 0).toString(16); + } if(android.os.Build.VERSION.SDK_INT < 19){ return images; } @@ -17,25 +21,28 @@ module.exports = function(__runtime__, scope){ images.pixel = rtImages.pixel; - images.detectsColor = rtImages.detectsColor.bind(rtImages); + images.detectsColor = function(img, color, x, y, threshold, algorithm){ + color = parseColor(color); + algorithm = algorithm || "rgb"; + threshold = threshold || 16; + var colorDetector = getColorDetector(color, algorithm, threshold); + var pixel = images.pixel(img, x, y); + return colorDetector.detectsColor(colors.red(pixel), colors.green(pixel), colors.blue(pixel)); + } images.findColor = function(img, color, options){ - if(typeof(color) == 'string'){ - if(color.startsWith('#')){ - color = parseInt('0x' + color.substring(1)); - }else{ - color = parseInt('0x' + color); - } - } + color = parseColor(color); options = options || {}; var region = options.region || []; - x = region[0] || 0; - y = region[1] || 0; - width = region[2] || (img.getWidth() - x); - height = region[3] || (img.getHeight() - y); - threads = options.threads || 2; - if(options.threshold !== 0){ - threshold = options.threshold || 8; + var x = region[0] || 0; + var y = region[1] || 0; + var width = region[2] || (img.getWidth() - x); + var height = region[3] || (img.getHeight() - y); + var threads = options.threads || 2; + if(options.similarity){ + var threshold = parseInt(255 * (1 - options.similarity)); + }else{ + var threshold = options.threshold || 16; } algorithm = options.algorithm || "rgb"; var rect = new android.graphics.Rect(x, y, width + x, height + y); @@ -76,7 +83,20 @@ module.exports = function(__runtime__, scope){ throw new Error("Unknown algorithm: " + algorithm); } + function parseColor(color){ + if(typeof(color) == 'string'){ + if(color.startsWith('#')){ + return parseInt('0x' + color.substring(1)); + }else{ + return parseInt('0x' + color); + } + } + return color; + } + scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findColor', 'findColorInRegion', 'findColorEquals']); + scope.colors = colors; + return images; } \ No newline at end of file diff --git a/autojs/src/main/assets/modules/__io__.js b/autojs/src/main/assets/modules/__io__.js index 2c6e4017..d5cae36e 100644 --- a/autojs/src/main/assets/modules/__io__.js +++ b/autojs/src/main/assets/modules/__io__.js @@ -1,5 +1,6 @@ module.exports = function(__runtime__, scope){ + scope.files = com.stardust.pio.PFile; scope.open = function(path, mode, encoding, bufferSize){ if(arguments.length == 1){ return com.stardust.pio.PFile.open(path); diff --git a/autojs/src/main/java/com/stardust/autojs/execution/RootedScriptExecution.java b/autojs/src/main/java/com/stardust/autojs/execution/RootedScriptExecution.java new file mode 100644 index 00000000..befbc6f7 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/execution/RootedScriptExecution.java @@ -0,0 +1,34 @@ +package com.stardust.autojs.execution; + +import com.stardust.autojs.ScriptEngineService; +import com.stardust.autojs.engine.ScriptEngine; +import com.stardust.autojs.runtime.ScriptRuntime; +import com.stardust.autojs.runtime.api.AbstractShell; +import com.stardust.autojs.runtime.api.ProcessShell; +import com.stardust.util.IntentExtras; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Stardust on 2017/7/16. + */ + +public class RootedScriptExecution extends RunnableScriptExecution { + + private static int count = 0; + private static Map arguments = new HashMap<>(); + + public RootedScriptExecution(ScriptEngineService service, ScriptExecutionTask task) { + super(service, task); + } + + @Override + public void run() { + } + + + public static void main(String[] args) { + } + +} diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java index 66b84337..d051e8ac 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java @@ -190,4 +190,12 @@ public abstract class AbstractShell { } public abstract void exitAndWaitFor(); + + public void sleep(long i) { + exec("sleep " + i); + } + + public void usleep(long l) { + exec("usleep " + l); + } } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/InputEventSender.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/InputEventSender.java new file mode 100644 index 00000000..7c60b680 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/InputEventSender.java @@ -0,0 +1,86 @@ +package com.stardust.autojs.runtime.api; + +import com.stardust.util.ScreenMetrics; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Created by Stardust on 2017/7/16. + */ + +public class InputEventSender { + private DataOutputStream mDeviceFile; + private ScreenMetrics mScreenMetrics; + + + public InputEventSender(String devicePath) throws FileNotFoundException { + mDeviceFile = new DataOutputStream(new FileOutputStream(devicePath)); + } + + public InputEventSender(int i) throws FileNotFoundException { + this("/dev/input/event" + i); + } + + public InputEventSender() { + + } + + + public void sendEvent(int type, int code, int value) throws IOException { + for (int i = 0; i < 16; i++) + mDeviceFile.writeByte(0); + mDeviceFile.writeShort(type); + mDeviceFile.writeShort(code); + mDeviceFile.writeInt(value); + mDeviceFile.flush(); + } + + + public void setInputDevice(int i) throws IOException { + if (mDeviceFile != null) { + mDeviceFile.close(); + } + mDeviceFile = new DataOutputStream(new FileOutputStream("/dev/input/event" + i)); + } + + public void Touch(int x, int y) throws IOException { + TouchX(x); + TouchY(y); + } + + public void setScreenMetrics(int width, int height) { + if (mScreenMetrics == null) { + mScreenMetrics = new ScreenMetrics(); + } + mScreenMetrics.setScreenMetrics(width, height); + } + + public void TouchX(int x) throws IOException { + sendEvent(3, 53, scaleX(x)); + } + + private int scaleX(int x) { + return mScreenMetrics.scaleX(x); + } + + public void TouchY(int y) throws IOException { + sendEvent(3, 54, scaleY(y)); + } + + private int scaleY(int y) { + return mScreenMetrics.scaleY(y); + + } + + + public void close() throws IOException { + mDeviceFile.close(); + } + +} + diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java index 4a116eb9..e5a4241d 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java @@ -40,19 +40,17 @@ public interface ColorDetector { class DifferenceDetector extends AbstractColorDetector { - private final int mRThreshold, mGThreshold, mBThreshold; + private final int mThreshold; public DifferenceDetector(int color, int threshold) { super(color); - mRThreshold = Color.red(threshold); - mGThreshold = Color.green(threshold); - mBThreshold = Color.blue(threshold); + mThreshold = threshold * 3; } + @Override public boolean detectsColor(int R, int G, int B) { - return Math.abs(R - mR) <= mRThreshold && Math.abs(G - mG) <= mGThreshold && - Math.abs(B - mB) <= mBThreshold; + return Math.abs(R - mR) + Math.abs(G - mG) + Math.abs(B - mB) <= mThreshold; } } @@ -77,7 +75,7 @@ public interface ColorDetector { public RGBDistanceDetector(int color, int threshold) { super(color); - mThreshold = threshold * threshold; + mThreshold = threshold * threshold * 3; } @Override @@ -100,7 +98,7 @@ public interface ColorDetector { mR = (color & 0xff0000) >> 16; mG = (color & 0x00ff00) >> 8; mB = color & 0xff; - mThreshold = threshold * threshold; + mThreshold = threshold * threshold * 8; } @Override @@ -163,7 +161,11 @@ public interface ColorDetector { long HS = getHS(mR, mG, mB); mH = (int) (HS & 0xffffffffL); mS = (int) ((HS >> 32) & 0xffffffffL); - mThreshold = threshold * threshold; + mThreshold = threshold * 3729600 / 255; + } + + public HSDistanceDetector(int color, float similarity) { + this(color, (int) (1.0f - similarity) * 255); } @Override @@ -191,9 +193,9 @@ public interface ColorDetector { H = 240 + (R - G) / (max - min) * 60; } if (H < 0) H = H + 360; - int S = (max - min) / max; + int S = (max - min) * 100 / max; return H & ((long) S << 32); } } -} +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java index d54fb3b9..09674a81 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java @@ -1,5 +1,6 @@ package com.stardust.autojs.runtime.api.image; +import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.media.Image; diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java index ed716af9..fc6616d4 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java @@ -7,6 +7,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.Point; import android.media.Image; import android.os.Build; import android.support.annotation.RequiresApi; @@ -71,6 +72,9 @@ public class Images { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public Image captureScreen() { mScriptRuntime.requiresApi(21); + if(mScreenCapturer == null){ + throw new SecurityException("No screen capture permission"); + } colorFinder.prestartThreads(); return mScreenCapturer.capture(); } @@ -122,19 +126,6 @@ public class Images { return bitmap.getPixel(x, y); } - public boolean detectsColor(Image image, int x, int y, int color) { - if (image == null) - return false; - int pixel = pixel(image, x, y); - return colorFinder.defaultColorDetector(color).detectsColor(Color.red(pixel), Color.green(pixel), Color.blue(pixel)); - } - - public boolean detectsColor(Image image, int x, int y, int color, int threshold) { - if (image == null) - return false; - int pixel = pixel(image, x, y); - return colorFinder.defaultColorDetector(color, threshold).detectsColor(Color.red(pixel), Color.green(pixel), Color.blue(pixel)); - } public static Bitmap read(String path) { return BitmapFactory.decodeFile(path); diff --git a/autojs/src/main/res/xml/accessibility_service_config.xml b/autojs/src/main/res/xml/accessibility_service_config.xml index 9c541b8b..62a664d6 100644 --- a/autojs/src/main/res/xml/accessibility_service_config.xml +++ b/autojs/src/main/res/xml/accessibility_service_config.xml @@ -1,6 +1,6 @@ eventTypes = new HashSet<>(); + private volatile AccessibilityNodeInfo mRootInActiveWindow; + private Timer mTimer; + private Handler mHandler; public static void addDelegate(int uniquePriority, AccessibilityDelegate delegate) { mDelegates.put(uniquePriority, delegate); @@ -59,15 +65,6 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi @Override public void onAccessibilityEvent(final AccessibilityEvent event) { Log.v(TAG, "onAccessibilityEvent: " + event); - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED - || event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER - || event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { - AccessibilityNodeInfo root = super.getRootInActiveWindow(); - if (root != null) { - mRootInActiveWindow = root; - Log.d(TAG, "rootInActiveWindow: " + mRootInActiveWindow); - } - } if (!containsAllEventTypes && !eventTypes.contains(event.getEventType())) return; for (Map.Entry entry : mDelegates.entrySet()) { @@ -93,10 +90,10 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi return mRootInActiveWindow; } - @Override public void onDestroy() { instance = null; + mTimer.cancel(); super.onDestroy(); } @@ -109,9 +106,36 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi LOCK.lock(); ENABLED.signalAll(); LOCK.unlock(); + mHandler = new Handler(); + mTimer = new Timer(); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + mHandler.post(new Runnable() { + @Override + public void run() { + AccessibilityNodeInfo root = superGetRootInActiveWindow(); + if (root != null) { + mRootInActiveWindow = root; + Log.d(TAG, "getRootInActiveWindow: " + root); + } + } + }); + + } + }, 0, 100); // FIXME: 2017/2/12 有时在无障碍中开启服务后这里不会调用服务也不会运行,安卓的BUG??? } + private AccessibilityNodeInfo superGetRootInActiveWindow() { + try { + return super.getRootInActiveWindow(); + } catch (IllegalStateException e) { + e.printStackTrace(); + return null; + } + } + public static boolean disable() { if (instance != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { instance.disableSelf(); diff --git a/automator/src/main/java/com/stardust/view/accessibility/LayoutInspectService.java b/automator/src/main/java/com/stardust/view/accessibility/LayoutInspectService.java new file mode 100644 index 00000000..9d72b7e5 --- /dev/null +++ b/automator/src/main/java/com/stardust/view/accessibility/LayoutInspectService.java @@ -0,0 +1,27 @@ +package com.stardust.view.accessibility; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.view.accessibility.AccessibilityNodeInfo; + +/** + * Created by Stardust on 2017/7/13. + */ + +public class LayoutInspectService extends AccessibilityService { + + private static LayoutInspectService instance; + + public static LayoutInspectService getInstance() { + return instance; + } + + + @Override + protected void onServiceConnected() { + AccessibilityServiceInfo info = getServiceInfo(); + info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; + setServiceInfo(info); + super.onServiceConnected(); + } +} + diff --git a/common/src/main/java/com/stardust/pio/PFile.java b/common/src/main/java/com/stardust/pio/PFile.java index 3aea3c8b..c474f088 100644 --- a/common/src/main/java/com/stardust/pio/PFile.java +++ b/common/src/main/java/com/stardust/pio/PFile.java @@ -2,11 +2,16 @@ package com.stardust.pio; import android.content.Context; import android.content.res.AssetManager; +import android.os.Environment; +import android.provider.MediaStore; + +import com.stardust.util.Func1; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -47,9 +52,16 @@ public class PFile { return open(path, "r", DEFAULT_ENCODING, DEFAULT_BUFFER_SIZE); } + public static boolean create(String path) { + try { + return new File(path).createNewFile(); + } catch (IOException e) { + return false; + } + } public static boolean createIfNotExists(String path) { - ensureDirectory(path); + ensureDir(path); File file = new File(path); if (!file.exists()) { try { @@ -61,7 +73,11 @@ public class PFile { return false; } - public static boolean ensureDirectory(String path) { + public static boolean exists(String path) { + return new File(path).exists(); + } + + public static boolean ensureDir(String path) { int i = path.lastIndexOf("\\"); if (i < 0) i = path.lastIndexOf("/"); @@ -89,8 +105,7 @@ public class PFile { try { return read(new FileInputStream(file), encoding); } catch (FileNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -104,8 +119,7 @@ public class PFile { is.read(bytes); return new String(bytes, encoding); } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -119,7 +133,7 @@ public class PFile { } public static boolean copyStream(InputStream is, String path) { - if (!ensureDirectory(path)) + if (!ensureDir(path)) return false; File file = new File(path); try { @@ -127,14 +141,15 @@ public class PFile { if (!file.createNewFile()) return false; FileOutputStream fos = new FileOutputStream(file); - return write(is, fos); - } catch (IOException e) { + write(is, fos); + return false; + } catch (IOException | UncheckedIOException e) { e.printStackTrace(); return false; } } - public static boolean write(InputStream is, OutputStream os) { + public static void write(InputStream is, OutputStream os) { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; try { while (is.available() > 0) { @@ -143,14 +158,45 @@ public class PFile { } is.close(); os.close(); - return true; } catch (IOException e) { - e.printStackTrace(); - return false; + throw new UncheckedIOException(e); } } + public static void write(String path, String text) { + write(new File(path), text); + } + + public static void write(String path, String text, String encoding) { + try { + write(new FileOutputStream(path), text, encoding); + } catch (FileNotFoundException e) { + throw new UncheckedIOException(e); + } + } + + public static void write(File file, String text) { + try { + write(new FileOutputStream(file), text); + } catch (FileNotFoundException e) { + throw new UncheckedIOException(e); + } + } + + public static void write(FileOutputStream fileOutputStream, String text) { + write(fileOutputStream, text, "utf-8"); + } + + + public static void write(OutputStream outputStream, String text, String encoding) { + try { + outputStream.write(text.getBytes(encoding)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public static boolean copy(String pathFrom, String pathTo) { try { return copyStream(new FileInputStream(pathFrom), pathTo); @@ -183,29 +229,6 @@ public class PFile { return fileName.substring(i + 1); } - public static boolean write(String path, String text) { - return write(new File(path), text); - } - - public static boolean write(File file, String text) { - try { - return write(new FileOutputStream(file), text); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return false; - } - } - - public static boolean write(OutputStream outputStream, String text) { - try { - outputStream.write(text.getBytes()); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - public static String generateNotExistingPath(String path, String extension) { if (!new File(path + extension).exists()) return path + extension; @@ -216,19 +239,18 @@ public class PFile { return pathI; i++; } - } - public static String getNameWithoutExtension(String fileName) { - int a = fileName.lastIndexOf('/'); - if (a < 0) - a = fileName.lastIndexOf('\\'); - if (a < 0) - a = -1; - int b = fileName.indexOf('.', a + 1); + public static String getName(String filePath) { + return new File(filePath).getName(); + } + + public static String getNameWithoutExtension(String filePath) { + String fileName = getName(filePath); + int b = fileName.lastIndexOf('.'); if (b < 0) b = fileName.length(); - fileName = fileName.substring(a + 1, b); + fileName = fileName.substring(0, b); return fileName; } @@ -257,6 +279,18 @@ public class PFile { return file.delete(); } + public static boolean remove(String path) { + return new File(path).delete(); + } + + public static boolean removeDir(String path) { + return deleteRecursively(new File(path)); + } + + public static String getSdcardPath() { + return Environment.getExternalStorageDirectory().getPath(); + } + public static String readAsset(AssetManager assets, String path) { try { return read(assets.open(path)); @@ -264,4 +298,36 @@ public class PFile { throw new UncheckedIOException(e); } } + + public static String[] listDir(String path) { + File file = new File(path); + return file.list(); + } + + public static String[] listDir(String path, final Func1 filter) { + final File file = new File(path); + return file.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return filter.call(name); + } + }); + } + + public static boolean isFile(String path) { + return new File(path).isFile(); + } + + public static boolean isDir(String path) { + return new File(path).isDirectory(); + } + + public static boolean isDirEmpty(String path) { + File file = new File(path); + return file.isDirectory() && file.list().length == 0; + } + + public static String join(String parent, String child) { + return new File(parent, child).getPath(); + } } diff --git a/common/src/main/java/com/stardust/pio/PRandomAccessBinaryFile.java b/common/src/main/java/com/stardust/pio/PRandomAccessBinaryFile.java index f1d81311..e1c621cd 100644 --- a/common/src/main/java/com/stardust/pio/PRandomAccessBinaryFile.java +++ b/common/src/main/java/com/stardust/pio/PRandomAccessBinaryFile.java @@ -27,4 +27,6 @@ public class PRandomAccessBinaryFile extends RandomAccessFile { return super.readLine(); } + + } diff --git a/version.json b/version.json index 78f35995..581618ea 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,7 @@ { - "versionCode": 147, - "versionName": "2.0.14 Beta", - "releaseNotes": "* 新增 自动添加群成员脚本\n* 优化 QQ名片点赞脚本(自动取消金豆)\n* 优化 空间点赞脚本\n* 新增 桌面小部件的支持\n* 修复 低分辨率下悬浮窗显示不全的问题\n* 修复 其他一些问题", + "versionCode": 151, + "versionName": "2.0.14 Beta2", + "releaseNotes": "* 修复 QQ加群成员、群成员点赞脚本(完美版)\n* 新增 互赞交流群(设置页面末尾)\n* 新增 批量同意好友验证脚本\n* 新增 文件读写、图色处理、控制台、一般常用函数等部分的文档\n* 修复 无障碍授权跳转失败的问题", "downloads" : [ { "name": "酷安", @@ -10,10 +10,6 @@ { "name": "应用宝", "url": "http://a.app.qq.com/o/simple.jsp?pkgname=com.stardust.scriptdroid" - }, - { - "name": "百度手机助手", - "url": "https://mobile.baidu.com/item?docid=11462633" } ], "oldVersions": [