| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /*
- * qqmusic_play_hongshanguo.js (稳定版 v3)
- * 通过 AutoX + QQ音乐 播放《红山果》
- *
- * 设计目标:
- * 1. 稳定可靠 —— 关键步骤带重试 / 轮询,不依赖固定 sleep。
- * 2. 不受网络不稳定影响 —— 搜索结果、播放缓冲采用“长轮询 + 重试 + 心跳”,
- * 轮询期间持续打印心跳,避免工具因静默超时断开。
- * 3. 未登录直接用游客态播放 —— 主动跳过登录/协议页,绝不卡在登录墙。
- * 4. 结果落盘 —— 末尾把结论写入 /sdcard/qqmusic_result.txt,便于离线核验。
- *
- * 关键实现说明:
- * - 搜索结果列表项:文字在“不可点击的 TextView”上,真正可点击的是其“父行”,
- * 故点击结果时必须找“含目标文字节点的最近可点击祖先”,而非要求自身可点击。
- * - 搜索框容器 clickable 可能为 false,点击其中心即可。
- */
- var PKG = "com.tencent.qqmusic";
- var TARGET = "红山果";
- var RESULT = "UNKNOWN";
- function log(s) { console.log(s); }
- function saveAndEnd() {
- try { files.write("/sdcard/qqmusic_result.txt", RESULT + " | " + new Date().toLocaleString()); } catch (e) { }
- log("RESULT=" + RESULT);
- throw new Error("END_SCRIPT");
- }
- function waitFor(cond, timeoutMs, intervalMs) {
- intervalMs = intervalMs || 500;
- var elapsed = 0, hb = 0;
- while (elapsed < timeoutMs) {
- if (cond()) return true;
- sleep(intervalMs); elapsed += intervalMs; hb += intervalMs;
- if (hb >= 2000) { log("..."); hb = 0; }
- }
- return cond();
- }
- function findNode(matcher) {
- try {
- var root = auto.root; if (!root) return null; var stack = [root];
- while (stack.length) {
- var node = stack.pop(); if (!node) continue;
- if (matcher(node)) return node;
- var n = node.childCount(); for (var i = 0; i < n; i++) stack.push(node.child(i));
- }
- } catch (e) { }
- return null;
- }
- function findAll(matcher) {
- var out = [];
- try {
- var root = auto.root; if (!root) return out; var stack = [root];
- while (stack.length) {
- var node = stack.pop(); if (!node) continue;
- if (matcher(node)) out.push(node);
- var n = node.childCount(); for (var i = 0; i < n; i++) stack.push(node.child(i));
- }
- } catch (e) { }
- return out;
- }
- // 文字包含 kw 的节点(排除输入框)
- function hasText(kw) {
- return findNode(function (n) {
- if (n.className() && n.className().indexOf("EditText") >= 0) return false;
- var t = n.text() || ""; return t.indexOf(kw) >= 0;
- }) != null;
- }
- // 点击“含目标文字节点的最近可点击祖先”(用于结果行)
- function tapResultRow(prefList) {
- var matches = findAll(function (n) {
- if (n.className() && n.className().indexOf("EditText") >= 0) return false;
- var t = n.text() || ""; return t.indexOf(TARGET) >= 0;
- });
- if (!matches.length) return false;
- // 优先匹配偏好关键词(如原唱“安与骑兵”、或“单曲”)
- var chosen = null;
- for (var p = 0; p < (prefList || []).length; p++) {
- for (var i = 0; i < matches.length; i++) {
- if ((matches[i].text() || "").indexOf(prefList[p]) >= 0) { chosen = matches[i]; break; }
- }
- if (chosen) break;
- }
- if (!chosen) chosen = matches[0];
- var node = chosen;
- while (node) {
- if (node.clickable() && (!node.className() || node.className().indexOf("EditText") < 0)) {
- click(node.bounds().centerX(), node.bounds().centerY()); return true;
- }
- node = node.parent();
- }
- return false;
- }
- function tapIdOnce(idStr) { var n = id(idStr).findOnce(); if (n) { click(n.bounds().centerX(), n.bounds().centerY()); return true; } return false; }
- function tapTextOnce(txt) { var n = text(txt).findOnce(); if (n) { click(n.bounds().centerX(), n.bounds().centerY()); return true; } return false; }
- function tapDescOnce(d) { var n = desc(d).findOnce(); if (n) { click(n.bounds().centerX(), n.bounds().centerY()); return true; } return false; }
- function isPlaying() {
- return findNode(function (node) {
- var t = node.text() || ""; var d = node.desc() || "";
- return (t.indexOf("暂停") >= 0 || d.indexOf("暂停") >= 0);
- }) != null;
- }
- function findPlayButton() {
- return findNode(function (node) {
- if (node.className() && node.className().indexOf("EditText") >= 0) return false;
- var t = node.text() || ""; var d = node.desc() || "";
- return (t.indexOf("播放") >= 0 || d.indexOf("播放") >= 0) && node.clickable();
- });
- }
- function onMainPage() {
- if (currentPackage() !== PKG) return false;
- var s = findNode(function (n) { var d = n.desc() || ""; return d.indexOf("搜索") >= 0; });
- if (s) return true;
- var h = findNode(function (n) { var t = n.text() || ""; return t.indexOf("首页") >= 0; });
- return h != null;
- }
- /* ---------- 1. 启动 ---------- */
- log("=== 1. 启动 QQ音乐 ===");
- try { home(); } catch (e) { } // 先回桌面,清掉残留的搜索/键盘焦点
- sleep(2000);
- var launched = false;
- for (var i = 0; i < 3; i++) {
- try { app.launchPackage(PKG); } catch (e) { try { app.launch(PKG); } catch (e2) { } }
- if (waitFor(function () { return currentPackage() === PKG; }, 10000)) { launched = true; break; }
- sleep(1000);
- }
- if (!launched) { RESULT = "ERR_LAUNCH"; log("ERR: 无法启动"); saveAndEnd(); }
- /* ---------- 2. 等待主页面(顺带跳过登录页)---------- */
- log("=== 2. 等待主页面 / 跳过登录(游客态)===");
- var mainOK = false;
- for (var g = 0; g < 10; g++) {
- if (onMainPage()) { mainOK = true; break; }
- log("尚未进入主页面,尝试跳过登录… (" + g + ")");
- tapIdOnce("com.tencent.qqmusic:id/fbi"); sleep(1000); if (onMainPage()) { mainOK = true; break; }
- tapTextOnce("取消"); sleep(1000); if (onMainPage()) { mainOK = true; break; }
- tapDescOnce("返回"); sleep(1000); if (onMainPage()) { mainOK = true; break; }
- tapTextOnce("跳过"); sleep(1000);
- }
- if (!mainOK) { RESULT = "ERR_NO_MAIN"; log("ERR: 始终未进入主页面"); saveAndEnd(); }
- log("已进入主页面");
- /* ---------- 3. 打开搜索框 ---------- */
- log("=== 3. 打开搜索框 ===");
- var searchBar = null;
- for (var k = 0; k < 10; k++) {
- searchBar = findNode(function (n) { var d = n.desc() || ""; return d.indexOf("搜索") >= 0; });
- if (searchBar) break; sleep(600);
- }
- if (!searchBar) { log("WARN: 坐标兜底点击搜索栏"); click(495, 96); sleep(1500); }
- else { click(searchBar.bounds().centerX(), searchBar.bounds().centerY()); sleep(1500); }
- /* ---------- 4. 输入 ---------- */
- log("=== 4. 输入 " + TARGET + " ===");
- var editBox = className("android.widget.EditText").findOne(4000);
- if (!editBox) { RESULT = "ERR_NO_EDIT"; log("ERR: 搜索输入框未出现"); saveAndEnd(); }
- editBox.setText(""); sleep(1000); editBox.setText(TARGET); sleep(2000)
- var c = className("android.widget.EditText").findOne(2000);
- log("搜索框内容: " + (c ? c.text() : "null"));
- sleep(1500);
- /* ---------- 5. 等待结果(网络容错)---------- */
- log("=== 5. 等待搜索结果(≤18s) ===");
- var ok = waitFor(function () { return hasText(TARGET); }, 18000, 800);
- if (!ok) { RESULT = "ERR_NO_RESULT"; log("ERR: 搜索结果未出现"); saveAndEnd(); }
- log("结果已出现");
- /* ---------- 6. 点击《红山果》结果(优先原唱 安与骑兵)---------- */
- log("=== 6. 点击《" + TARGET + "》歌曲 ===");
- if (!tapResultRow(["安与骑兵", "单曲", "儿歌", "童声"])) { RESULT = "ERR_NO_TAP"; log("ERR: 未点到结果"); saveAndEnd(); }
- sleep(3000);
- // 若仍在搜索输入态(仅补全了文字未跳转),提交搜索(回车)后再点一次
- if (!isPlaying() && className("android.widget.EditText").findOne(1500)) {
- log("尝试提交搜索(回车)");
- try { shell("input keyevent 66"); } catch (e) { log("shellErr:" + e); }
- sleep(2500);
- waitFor(function () { return hasText(TARGET); }, 12000, 800);
- tapResultRow(["安与骑兵", "单曲", "儿歌", "童声"]);
- sleep(3000);
- }
- /* ---------- 7. 播放(游客态 + 重试)---------- */
- log("=== 7. 播放 ===");
- if (isPlaying()) { RESULT = "OK_PLAYING"; log("已在播放"); saveAndEnd(); }
- var playOk = false;
- for (var p = 0; p < 6 && !playOk; p++) {
- log("播放尝试 " + (p + 1));
- var pb = findPlayButton(); if (pb) { click(pb.bounds().centerX(), pb.bounds().centerY()); }
- playOk = waitFor(isPlaying, 6000, 500);
- if (playOk) break;
- tapResultRow(["安与骑兵", "单曲", "儿歌", "童声"]); sleep(1000);
- let res1 = id("com.tencent.qqmusic:id/k0i").findOne(1000)
- console.log(res1)
- if (res1) {
- let hongShanGuo = res1.findOne(text("红山果"))
- let anQiBin = res1.findOne(text("安与骑兵·安与骑兵"))
- if (hongShanGuo && anQiBin) {
- console.log("找到歌曲")
- click(hongShanGuo.bounds().centerX(), hongShanGuo.bounds().centerY())
- sleep(2000)
- break
- }
- }
- }
- if (playOk) { RESULT = "OK_PLAYING"; log("SUCCESS: 《" + TARGET + "》已开始播放(游客态)"); }
- else {
- log("WARN: 未检测到播放态,尝试兜底电台");
- var fb = tapDescOnce("播放猜你喜欢") || tapDescOnce("播放");
- if (fb) playOk = waitFor(isPlaying, 10000, 500);
- if (playOk) { RESULT = "OK_PLAYING_RADIO"; log("SUCCESS(兜底电台): 已进入播放"); }
- else { RESULT = "FAIL_NEED_LOGIN"; log("FAIL: 游客态无法播放该指定歌曲,需登录后才能播放"); }
- }
- saveAndEnd();
|