AutojsPro - 豆角酱油(QQ信息组件)1. openQQ(qqNum)方法跳转 QQ 名片2. sendText(message)发送文本3. 点击控件 1.btn_page() 名片页点击发消息2.btn_send() 消息框点击发送3.menu() 打开+4.function clickGiftButton() 点击礼物5.closeAd() 关闭广告4. btn_over(x,y)基于坐标换算成当前屏幕上的对应坐标,实现跨机型的比例点击5. readContactList(filePath)从文件读取联系人信息6. sendMessage(qqNum, message)发送消息给指定QQ7. findAnyImg(imgArray, num) 屏幕上识别目标图片数组中的任意一张,找到后按指定次数点击8. logToFile(message)记录日志到文件(追加模式)9. 常用服务10. getRandomLineFromFile(filePath)方法从文件中随机读取一行文本,并忽略行首的数字序号和标点11.saveToFile(content, fileName = null, dir = "")方法保存内容到文件(带错误处理和智能命名)
xxxxxxxxxx
// 定义跳转 QQ 名片的函数,接收 QQ 号码作为参数
function openQQ(qqNum) {
// try-catch 用于捕获可能出现的异常,避免程序因错误崩溃
try {
// 调用 app 的 startActivity 方法,发起跳转动作
// action: "VIEW" 表示执行查看操作
// data 拼接 QQ 名片跳转的协议,通过 mqqapi 协议传递 QQ 号等参数,实现打开对应 QQ 名片
// packageName 指定要跳转的应用包名,这里是手机 QQ 的包名 "com.tencent.mobileqq"
app.startActivity({
action: "VIEW",
data: "mqqapi://card/show_pslcard?src_type=internal&version=1&uin=" +
qqNum + "&card_type=person&source=external",
packageName: "com.tencent.mobileqq"
});
// 线程休眠 3000 毫秒(3 秒),等待跳转操作完成、应用启动
sleep(3000);
// 获取当前前台运行的应用包名,判断是否等于手机 QQ 的包名
// 如果等于,说明跳转成功进入了 QQ ,返回 true ;否则返回 false
return currentPackage() === "com.tencent.mobileqq";
} catch (e) {
// 如果 try 块中代码执行出错,进入 catch 块
// 弹出 toast 提示跳转 QQ 失败,并显示具体的错误信息
toast("跳转QQ失败: " + e);
// 返回 false 表示跳转操作失败
return false;
}
}
xxxxxxxxxx
// 发送文本
function sendText(message) {
try {
let inputArea = className("EditText").findOne(5000);
if (!inputArea) {
// 尝试其他方式查找输入框
inputArea = descMatches("发送消息|输入消息").findOne(2000);
}
if (!inputArea) {
toast("未找到输入框");
return false;
}
inputArea.click();
sleep(1000);
// 清空输入框
inputArea.setText("");
sleep(500);
// 输入文本
setText(message);
sleep(1500);
// 点击发送按钮
let sendBtn = text("发送").findOne(2000) ||
desc("发送").findOne(2000) ||
id("com.tencent.mobileqq:id/fun_btn").findOne(2000);
if (sendBtn) {
sendBtn.click();
return true;
}
// 如果找不到发送按钮,使用回车键发送
KeyCode("KEYCODE_ENTER");
return true;
} catch (e) {
toast("发送文本失败: " + e);
return false;
}
}
xxxxxxxxxx
//名片页点击发消息
function btn_page(){
let target = text("发消息").id("p0b").findOne(5000);
if (target) {
target.click();
toast("已点击控件");
} else {
toast("5秒内未找到控件");
}
}
//消息框点击发送
function btn_send(){
// 定位并点击控件(最多等待5秒)
let sendBtn = text("发送").id("send_btn").findOne();
if (sendBtn) {
sendBtn.click();
toast("已点击发送按钮");
} else {
toast("5秒内未找到发送按钮");
}
}
// 展开功能菜单
function menu(){
let menu = desc('更多功能').findOne(3000);
if (menu) {
menu.click();
toast("菜单已展开");
} else {
click(device.width - 50, 100); // 右上角菜单
}
sleep(500);
}
/**
* 尝试定位并点击"礼物"按钮
* @returns {boolean} 是否成功点击礼物按钮
*/
function clickGiftButton() {
// 等待界面加载完成
sleep(500);
// 尝试通过desc或text定位礼物按钮(设置超时时间为3秒)
let giftButton = desc("礼物").findOne(3000) || text("礼物").findOne(3000);
if (giftButton) {
// 增加点击前的确认,避免误触
log("已定位到礼物按钮,准备点击");
// 点击按钮并等待页面跳转
let isClicked = giftButton.click();
sleep(1000);
if (isClicked) {
toast("成功进入礼物页");
return true;
} else {
toast("点击礼物按钮失败");
return false;
}
} else {
// 记录详细的错误信息
toast("错误:找不到礼物入口,可能界面元素已变化");
return false;
}
}
function closeAd(){
// 尝试关闭广告
let closeAd = desc('关闭广告').findOne(3000);
if (closeAd) {
closeAd.click();
}
}
xxxxxxxxxx
function btn_over(x,y){
// 原坐标(基于原分辨率的点)
var originalX = x;
var originalY = y;
// 原分辨率(记录这个点所在的屏幕尺寸)
var originalWidth = 1440;
var originalHeight = 3200;
// 获取当前设备的屏幕尺寸
var currentWidth = device.width;
var currentHeight = device.height;
// 计算比例缩放后的坐标(四舍五入取整数)
var clickX = Math.round(originalX * (currentWidth / originalWidth));
var clickY = Math.round(originalY * (currentHeight / originalHeight));
// 执行点击
click(clickX, clickY);
console.log("适配后点击坐标:", clickX, clickY);
}
btn_over(1085,3096)//点击表情
sleep(1000)
btn_over(600,3124)//点击收藏表情
sleep(1000)
btn_over(920,2370)//发送第一张
xxxxxxxxxx
/**
* 从文件读取联系人信息
* @param {string} filePath 文件路径
* @returns {Array} 联系人数组
*/
function readContactList(filePath) {
try {
// 检查文件是否存在
if (!files.exists(filePath)) {
toast("联系人文件不存在: " + filePath);
exit();
}
// 读取文件内容
let content = files.read(filePath);
if (!content) {
toast("文件内容为空: " + filePath);
exit();
}
// 按行分割内容
let lines = content.split(/\r?\n/);
let contacts = [];
// 解析每行数据
for (let line of lines) {
// 跳过空行和注释行
if (!line.trim() || line.trim().startsWith("#")) {
continue;
}
// 分割地区、昵称和QQ号(支持多种分隔符)
let parts = line.split(/[|,-]/);
if (parts.length < 2) {
continue;
}
// 提取并处理地区和QQ号
let region = parts[0].trim();
let qq = parts[parts.length - 1].replace(/\D/g, '');
// 验证并添加有效联系人
if (region && qq.length >= 5) {
contacts.push({
region: region,
qq: qq
});
}
}
// 检查是否有有效联系人
if (contacts.length === 0) {
toast("未找到有效联系人");
exit();
}
return contacts;
} catch (e) {
toast("读取联系人文件失败: " + e);
exit();
}
}
xxxxxxxxxx
/**
* 发送消息给指定QQ
* @param {string} qqNum - QQ号码
* @param {string} message - 要发送的消息
*/
function sendMessage(qqNum, message) {
try {
// 记录开始时间
let startTime = new Date().getTime();
// 打开QQ并检查是否成功
if (!openQQ(qqNum)) {
toast("无法跳转到QQ: " + qqNum);
return;
}
sleep(3000);
// 查找发消息按钮(多种匹配方式)
let sendMsgBtn = textContains("发消息").findOne(3000) ||
descContains("发消息").findOne(3000) ||
className("android.widget.Button").text("发消息").findOne(3000);
// 点击发消息按钮或屏幕底部
if (sendMsgBtn) {
sendMsgBtn.click();
sleep(2000);
} else {
// 尝试点击屏幕底部中间位置
click(device.width / 2, device.height - 100);
sleep(2000);
}
// 发送文本消息
if (!sendText(message)) {
toast("文本消息发送失败");
return;
}
sleep(1500);
btn_over(1085,3096)//点击表情
sleep(1000)
btn_over(600,3124)//点击收藏表情
sleep(1000)
btn_over(920,2370)//发送第一张
sleep(1500);
// 执行QQ操作
// 返回主界面
back();
sleep(1000);
back();
sleep(1000);
// 计算并显示耗时
let timeUsed = (new Date().getTime() - startTime) / 1000;
toast("发送给 " + qqNum + " 完成,耗时: " + timeUsed.toFixed(1) + "秒");
} catch (e) {
toast("发送消息给 " + qqNum + " 失败: " + e);
}
}
xxxxxxxxxx
/**
* 图片识别与点击函数 - 功能:在屏幕上识别目标图片数组中的任意一张,找到后按指定次数点击
* @param {Array<string>} imgArray - 目标图片路径数组(需要识别的图片路径集合)
* @param {number} num - 找到图片后需要点击的次数
* @returns {boolean} 识别并点击成功返回true,未找到或识别失败返回false
*/
function findAnyImg(imgArray, num) {
// 存储成功读取的目标图片对象(用于后续识别)
let targetImages = [];
// 遍历图片路径数组,读取每张图片并存储
for (let i = 0; i < imgArray.length; i++) {
// 读取图片文件:images.read()接收图片路径,返回图片对象
let img = images.read(imgArray[i]);
// 如果图片读取成功,添加到目标图片数组
if (img) {
targetImages.push(img);
} else {
// 读取失败时,弹出提示显示失败的图片路径
toast("读取图片失败: " + imgArray[i]);
}
}
// 检查是否有成功读取的目标图片
if (targetImages.length === 0) {
toast("所有图片读取失败");
return false; // 无可用图片,直接返回失败
}
// 配置最大识别尝试次数(防止无限循环),初始化"是否找到图片"标记
let maxTry = 10, found = false;
// 循环尝试识别图片,最多尝试maxTry次
for (let attempt = 0; attempt < maxTry; attempt++) {
// 截取当前屏幕画面,用于后续识别
let screenshot = images.captureScreen();
// 如果截图失败,等待500毫秒后继续下一次尝试
if (!screenshot) {
sleep(500);
continue;
}
// 存储找到的图片坐标点(初始为null,未找到)
let foundPoint = null;
// 遍历目标图片数组,逐个与截图比对
for (let i = 0; i < targetImages.length; i++) {
// 识别图片:在截图中查找当前目标图片
// threshold: 0.7 表示匹配度阈值(70%以上视为匹配成功)
let point = images.findImage(screenshot, targetImages[i], {
threshold: 0.7
});
// 如果找到匹配的图片,记录坐标并退出循环
if (point) {
foundPoint = point;
break;
}
}
// 如果找到目标图片的坐标
if (foundPoint) {
// 按指定次数点击该坐标
for (let clickCount = 0; clickCount < num; clickCount++) {
click(foundPoint.x, foundPoint.y); // 点击坐标(x,y)
sleep(300); // 每次点击后等待300毫秒,防止连续点击过快
}
// 标记为找到图片
found = true;
// 释放截图资源(避免内存占用)
screenshot.recycle();
// 退出识别循环(已找到目标,无需继续尝试)
break;
}
// 未找到图片时,释放截图资源
screenshot.recycle();
// 等待500毫秒后进行下一次识别尝试
sleep(500);
}
// 释放所有目标图片资源(遍历数组,逐个释放)
targetImages.forEach(function(img) {
img.recycle();
});
// 返回最终结果:是否成功找到并点击图片
return found;
}
xxxxxxxxxx
/**
* 记录日志到文件(追加模式)
* @param {string} message - 日志内容
*/
function logToFile(message) {
const LOG_FILE = "weather_send_log.txt";
const timestamp = new Date().toLocaleString();
const logEntry = `[${timestamp}] ${message}\n`;
// 追加日志到文件
files.append(LOG_FILE, logEntry);
}
无障碍启动服务1
xxxxxxxxxx
auto.waitFor();
// 检查无障碍服务
if (!auto.service) {
app.startActivity({ action: "android.settings.ACCESSIBILITY_SETTINGS" });
toast("请开启无障碍服务");
while (!auto.service) sleep(1000);
toast("无障碍服务已开启");
}
// 申请截图权限
if (!requestScreenCapture()) {
toast("请求截图失败");
exit();
}
无障碍启动服务2
xxxxxxxxxx
sleep(1000)
// 检查无障碍服务是否开启
if (!auto.service) {
// 如果无障碍服务未开启,则跳转到无障碍设置页面
app.startActivity({
action: "android.settings.ACCESSIBILITY_SETTINGS"
});
// 提示用户开启服务
toast("请找到本应用并开启无障碍服务");
// 循环检查直到服务开启
while (!auto.service) {
sleep(1000);
}
// 服务已开启,提示用户
toast("无障碍服务已开启,继续执行脚本");
}
// 无障碍服务已开启,继续执行后面的代码
toast("开始执行主脚本");
// 在这里添加你的主要代码...
// 示例代码
console.log("无障碍服务已开启,脚本正常运行");
截图权限
xxxxxxxxxx
/* 申请屏幕截图权限. */
if(!requestScreenCapture()){
toast("请求截图失败");
exit();
}
屏幕常亮
xxxxxxxxxx
// 保持屏幕常亮:调用device对象的keepScreenOn()方法,确保脚本运行期间屏幕不会自动熄灭
// 避免因屏幕休眠导致的界面元素无法识别或操作中断
device.keepScreenOn();
// 取消屏幕常亮:脚本主逻辑执行完毕后,调用cancelKeepingAwake()方法,恢复系统默认的屏幕休眠设置
// 避免脚本结束后屏幕一直亮着消耗设备电量
device.cancelKeepingAwake();
调用shizuki ADB权限
xxxxxxxxxx
/* 检查是否有adb权限
* $shell.checkAccess("adb") 用于检测当前脚本是否拥有adb权限
* adb权限是执行部分系统级操作(如强制关闭应用、模拟系统输入)的必要条件
* 缺少此权限会导致后续依赖adb的命令执行失败
*/
var isAdb = $shell.checkAccess("adb");
// 如果没有adb权限,弹出提示并退出脚本
if (!isAdb) {
alert("请先使用$shell授权,再运行"); // 提示用户需要通过$shell工具授予adb权限
exit(); // 终止脚本执行,因为核心功能依赖adb权限
}
// 等待1秒,确保权限检查和提示弹窗的操作完全完成
// 避免因弹窗未关闭导致后续操作受干扰
sleep(1000);
/* 申请屏幕截图权限
* requestScreenCapture() 向系统请求屏幕截图权限(属于敏感权限,需用户手动授权)
* 许多自动化操作(如图片识别、界面元素定位)依赖屏幕截图功能
*/
if (!requestScreenCapture()) {
toast("请求截图失败"); // 以轻量提示告知用户截图权限申请失败
exit(); // 终止脚本,因为截图是后续操作(如图片识别)的基础
}
// 将shell操作默认切换到adb模式
// 后续通过$shell执行的命令会默认使用adb方式运行,确保操作拥有足够权限
// 适用于需要系统级权限的操作(如关闭其他应用)
$shell.setDefaultOptions({ adb: true });
调用shizuki ADB权限下关闭程序
xxxxxxxxxx
/**
* 关闭指定应用程序(增强版)
* @param {string} packageName 应用程序包名
* @param {number} [timeout=3000] 等待应用关闭的最大时间(毫秒)
* @returns {boolean} 应用是否成功关闭
*/
function closeApp(packageName, timeout = 3000) {
try {
// 执行关闭应用命令
const result = $shell(`am force-stop ${packageName}`, true);
// 检查命令执行状态
if (!result.success) {
throw new Error(`命令执行失败: ${result.error}`);
}
// 验证应用是否已关闭(通过检查进程是否存在)
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
// 通过ps命令检查应用进程是否还在运行
const psResult = $shell(`ps | grep ${packageName}`, true);
// 如果没有找到匹配的进程,说明应用已关闭
if (!psResult.stdout.trim()) {
return true;
}
sleep(300); // 等待一段时间后再次检查
}
throw new Error(`超时: 应用 ${packageName} 未在 ${timeout}ms 内关闭`);
} catch (e) {
log(`关闭应用 ${packageName} 失败: ${e.message}`);
return false;
}
}
以下是对代码的详细解释:
从文件中随机读取一行文本,并忽略行首的数字序号和标点
@param {string} filePath 文件路径
@returns {string} 随机选择的文本行(已清理行首序号)
xxxxxxxxxx
/**
* 从文件中随机读取一行文本,并忽略行首的数字序号和标点
* @param {string} filePath 文件路径
* @returns {string} 随机选择的文本行(已清理行首序号)
*/
function getRandomLineFromFile(filePath) {
sleep(1000)
try {
// 读取文件内容
let content = files.read(filePath);
if (!content) {
toast("文件内容为空: " + filePath);
return "每日分享:~o( =∩ω∩= )m"; // 默认文本
}
// 分割为行数组并过滤空行
let lines = content.split(/\r?\n/).filter(line => line.trim() !== "");
if (lines.length === 0) {
return "每日分享:~o( =∩ω∩= )m"; // 默认文本
}
// 随机选择一行
let randomIndex = Math.floor(Math.random() * lines.length);
let selectedLine = lines[randomIndex];
// 清理行首的序号和标点(如"1. ", "2) "等格式)
let cleanedLine = selectedLine.replace(/^\s*\d+[.)]\s*/, '');
// 如果清理后内容为空,则使用原始行
return cleanedLine.trim() || selectedLine;
} catch (e) {
toast("读取文件失败: " + filePath);
return "每日分享:~o( =∩ω∩= )m"; // 默认文本
}
}
以下是对代码的详细解释:
保存内容到文件(带错误处理和智能命名)
@param {string} content - 要保存的内容
@param {string} [fileName="report.txt"] - 文件名(可选,默认使用时间戳命名)
@param {string} [dir=""] - 保存目录(可选,默认为脚本当前目录)
@returns {boolean} 保存成功返回true,失败返回false
xxxxxxxxxx
/**
* 保存内容到文件(带错误处理和智能命名)
* @param {string} content - 要保存的内容
* @param {string} [fileName="report.txt"] - 文件名(可选,默认使用时间戳命名)
* @param {string} [dir=""] - 保存目录(可选,默认为脚本当前目录)
* @returns {boolean} 保存成功返回true,失败返回false
*/
function saveToFile(content, fileName = null, dir = "") {
try {
// 处理默认文件名(使用时间戳避免重复)
if (!fileName) {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
fileName = `report_${timestamp}.txt`;
}
// 构建完整文件路径
const saveDir = dir || files.cwd();
const filePath = files.join(saveDir, fileName);
// 确保目录存在
if (!files.ensureDir(saveDir)) {
throw new Error(`创建目录失败: ${saveDir}`);
}
// 写入文件(使用utf-8编码)
const success = files.write(filePath, content);
if (!success) {
throw new Error(`写入文件失败: ${filePath}`);
}
// 显示保存成功提示(包含文件大小信息)
const fileSize = (files.size(filePath) / 1024).toFixed(2);
toast(`文件已保存: ${filePath} (${fileSize} KB)`);
log(`保存成功: ${filePath}`);
return true;
} catch (e) {
// 错误处理(区分不同类型的错误)
loge(`保存文件失败: ${e.message}`);
toast(`保存失败: ${e.message}`);
return false;
}
}