| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 |
- findWidget = function (object) {
- try {
- const {
- text: textValue,
- textMatches: textMatchesValue,
- id: idValue,
- className: classValue,
- depth: depthValue,
- indexInParent: indexValue,
- boundsInside: boundsValue,
- } = object;
- // 归一化正则:本运行时(Rhino)把 RegExp 对象直接传给 Java 的 textMatches/descMatches 时
- // 会走 toString() 变成 "/pattern/flags" 字面量(首尾斜杠和标志位成为待匹配内容),
- // 导致永远匹配不到控件。这里统一转成 Java Pattern 字符串,i/m/s 标志转内联 (?i)(?m)(?s)
- let matchesPattern = textMatchesValue;
- if (matchesPattern instanceof RegExp) {
- let inlineFlags = "";
- if (matchesPattern.ignoreCase) inlineFlags += "(?i)";
- if (matchesPattern.multiline) inlineFlags += "(?m)";
- if (matchesPattern.dotAll) inlineFlags += "(?s)";
- matchesPattern = inlineFlags + matchesPattern.source;
- }
- // 根据传入的字段自动判断查找方式
- const hasText = textValue !== undefined;
- const hasTextMatches = textMatchesValue !== undefined;
- const hasId = idValue !== undefined;
- const hasClass = classValue !== undefined;
- const hasDepth = depthValue !== undefined;
- const hasIndex = indexValue !== undefined;
- const hasBounds = boundsValue !== undefined;
- // 创建基础选择器
- let selector = null;
- // 构建选择器链
- if (hasText) {
- // 从文本或描述开始(探测需带 visibleToUser(true),与最终查询一致,否则不可见控件命中会导致最终查不到)
- selector = text(textValue).visibleToUser(true).findOnce() ? text(textValue) : desc(textValue);
- } else if (hasTextMatches) {
- // 从文本匹配开始(同上,探测带可见性过滤)
- selector = textMatches(matchesPattern).visibleToUser(true).findOnce() ? textMatches(matchesPattern) : descMatches(matchesPattern);
- } else if (hasId) {
- // 从ID开始
- selector = id(idValue);
- } else if (hasClass) {
- // 从类名开始
- selector = className(classValue);
- } else if (hasDepth) {
- // 从深度开始
- selector = depth(depthValue);
- } else if (hasIndex) {
- // 从索引开始
- selector = indexInParent(indexValue);
- } else if (hasBounds) {
- // 从边界开始
- if (Array.isArray(boundsValue) && boundsValue.length === 4) {
- selector = boundsInside(
- boundsValue[0],
- boundsValue[1],
- boundsValue[2],
- boundsValue[3]
- );
- }
- }
- // 如果没有匹配到任何条件,返回null
- if (!selector) return null;
- // 添加其他条件
- if (hasText &&
- !(
- selector.toString().includes("text") ||
- selector.toString().includes("desc")
- )
- ) {
- selector =
- selector.textContains(textValue) || selector.descContains(textValue);
- }
- if (hasTextMatches &&
- !(
- selector.toString().includes("textMatches") ||
- selector.toString().includes("descMatches")
- )
- ) {
- selector =
- selector.textMatches(matchesPattern) || selector.descMatches(matchesPattern);
- }
- if (hasId && !selector.toString().includes("id")) {
- selector = selector.id(idValue);
- }
- if (hasClass && !selector.toString().includes("className")) {
- selector = selector.className(classValue);
- }
- if (hasDepth && !selector.toString().includes("depth")) {
- selector = selector.depth(depthValue);
- }
- if (hasIndex && !selector.toString().includes("indexInParent")) {
- selector = selector.indexInParent(indexValue);
- }
- if (hasBounds && !selector.toString().includes("boundsInside")) {
- if (Array.isArray(boundsValue) && boundsValue.length === 4) {
- selector = selector.boundsInside(
- boundsValue[0],
- boundsValue[1],
- boundsValue[2],
- boundsValue[3]
- );
- }
- }
- return selector.visibleToUser(true).findOnce();
- } catch (error) {
- console.log("findWidget error: ", error);
- console.log("findWidget failed on:", object);
- return null;
- }
- };
- widgetBounds = function (widget, returnType) {
- try {
- if (!widget) {
- return false;
- }
- const bool =
- widget.bounds().left < widget.bounds().right &&
- widget.bounds().left >= 0 &&
- widget.bounds().top < widget.bounds().bottom &&
- widget.bounds().top >= 0;
- if (bool && returnType == "object") {
- return {
- left: widget.bounds().left,
- top: widget.bounds().top,
- right: widget.bounds().right,
- bottom: widget.bounds().bottom,
- };
- }
- return bool;
- } catch (error) {
- console.log("widgetBounds error: ", error);
- }
- return false;
- };
- clickWidget = function (widget, ranNum) {
- try {
- if (!widget) {
- return false;
- }
- ranNum = ranNum || 1;
- const x = random(
- widget.bounds().left + ranNum,
- widget.bounds().right - ranNum
- );
- const y = random(
- widget.bounds().top + ranNum,
- widget.bounds().bottom - ranNum
- );
- const time = random(150, 280);
- sleep(random(500, 1000));
- return press(x, y, time);
- // const result = shell("input tap ".concat(x, " ", y), true);
- // return result.code === 0;
- } catch (error) {
- console.log("clickWidget coordinate error:", error);
- }
- console.log("Failed widget:", widget);
- return false;
- };
- // 从 0~10(含)中随机取 1~3 个不重复的数
- function randomPick(len) {
- len = len || 10
- const count = Math.floor(Math.random() * 3) + 1; // 1~3 个
- const pool = Array.from({ length: len }, (_, i) => i); // [0..10]
- // 洗牌
- for (let i = pool.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [pool[i], pool[j]] = [pool[j], pool[i]];
- }
- return pool.slice(0, count);
- }
- /*
- let interestsArr = []
- let interests = id("com.zhiliaoapp.musically:id/kj5").find()
- if (interests.nonEmpty()) {
- for (let i = 0; i < interests.length; i++) {
- let element = interests[i];
- if (widgetBounds(element)) {
- interestsArr.push(element)
- }
- }
- console.log("interestsArr:", interestsArr.length)
- if (interestsArr.length > 0) {
- let chooseInterestsArr = randomPick(interestsArr.length)
- console.log("chooseInterestsArr:", chooseInterestsArr.length)
- for (let index = 0; index < chooseInterestsArr.length; index++) {
- console.log("choose interests:" + index)
- let currentIndex = chooseInterestsArr[index]
- clickWidget(interestsArr[currentIndex])
- sleep(1000)
- }
- clickWidget(findWidget({ text: "Next" }))
- }
- }
- console.log(randomPick(10)) */
- const TIKTOK_UA = "Mozilla/5.0 (Linux; Android 12; Pixel 5a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36";
- /**
- * 正则特殊字符转义
- */
- function escapeRegex(s) {
- return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- }
- /**
- * 读取 HTTP 响应体(UTF-8),读完自动断开连接。
- * 4xx/5xx 时改读错误流,避免 getInputStream 抛 FileNotFoundException。
- * @returns {string} 响应内容(可能为空字符串)
- */
- function readHttpBody(conn) {
- try {
- let code = conn.getResponseCode();
- let stream = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
- if (!stream) {
- conn.disconnect();
- return "";
- }
- let reader = new java.io.BufferedReader(new java.io.InputStreamReader(stream, "UTF-8"));
- let sb = new java.lang.StringBuilder();
- let line;
- while ((line = reader.readLine()) !== null) {
- sb.append(line);
- }
- reader.close();
- conn.disconnect();
- return sb.toString();
- } catch (e) {
- try {
- conn.disconnect();
- } catch (e2) {}
- throw e;
- }
- }
- let TIKTOK_TTWID = null;
- /**
- * 获取 ttwid Cookie(TikTok 接口风控校验用,首页 Set-Cookie 下发)。
- * 获取一次后缓存复用;失败不影响主流程(降级走 HTML 解析)。
- */
- function fetchTtwidCookie() {
- if (TIKTOK_TTWID) return TIKTOK_TTWID;
- try {
- let conn = new java.net.URL("https://www.tiktok.com/").openConnection();
- conn.setConnectTimeout(8000);
- conn.setReadTimeout(8000);
- conn.setRequestProperty("User-Agent", TIKTOK_UA);
- conn.getResponseCode();
- let map = conn.getHeaderFields();
- let list = map ? map.get("Set-Cookie") : null;
- if (list) {
- for (let i = 0; i < list.size(); i++) {
- let c = String(list.get(i));
- let idx = c.indexOf("ttwid=");
- if (idx !== -1) {
- TIKTOK_TTWID = c.substring(idx).split(";")[0]; // 形如 ttwid=xxx
- console.log("fetchTtwidCookie: " + TIKTOK_TTWID);
- break;
- }
- }
- }
- conn.disconnect();
- } catch (e) {
- console.log("fetchTtwidCookie: 获取失败 " + e);
- }
- return TIKTOK_TTWID;
- }
- /**
- * GET 请求并返回响应体字符串;网络异常 / 非 200 / 空响应统一返回 null(只留一行日志)。
- */
- function httpGetRaw(url, headers, tag) {
- try {
- let conn = new java.net.URL(url).openConnection();
- conn.setConnectTimeout(8000);
- conn.setReadTimeout(8000);
- conn.setRequestProperty("User-Agent", TIKTOK_UA);
- let extra = headers || {};
- for (let k in extra) {
- if (extra.hasOwnProperty(k)) conn.setRequestProperty(k, extra[k]);
- }
- let code = conn.getResponseCode();
- let body = readHttpBody(conn);
- if (code !== 200 || !body || body.trim() === "") {
- console.log((tag || "httpGetRaw") + ": HTTP " + code + " 响应为空,跳过");
- return null;
- }
- return body;
- } catch (e) {
- console.log((tag || "httpGetRaw") + ": 请求失败 " + e);
- return null;
- }
- }
- /**
- * GET 请求并解析 JSON;失败返回 null
- */
- function httpGetJson(url, headers, tag) {
- let body = httpGetRaw(url, headers, tag);
- if (body === null) return null;
- try {
- return JSON.parse(body);
- } catch (e) {
- console.log((tag || "httpGetJson") + ": JSON 解析失败");
- return null;
- }
- }
- /**
- * 解析 TikTok 短链为最终完整链接(含用户名与视频ID)。
- * 手动跟随 301/302 重定向(最多 8 跳),命中 /video/ 链接提前结束。
- * @param {string} url 短链(如 https://www.tiktok.com/t/ZTy8r7T2a/)
- * @returns {string} 最终链接,解析失败时返回原链接
- */
- function resolveTikTokUrl(url) {
- try {
- let current = url;
- for (let i = 0; i < 8; i++) {
- let conn = new java.net.URL(current).openConnection();
- conn.setInstanceFollowRedirects(false);
- conn.setConnectTimeout(8000);
- conn.setReadTimeout(8000);
- conn.setRequestProperty("User-Agent", TIKTOK_UA);
- conn.setRequestMethod("GET");
- conn.connect();
- let loc = conn.getHeaderField("Location");
- conn.disconnect();
- if (loc) {
- current = new java.net.URL(new java.net.URL(current), loc).toString();
- if (current.indexOf("/video/") !== -1) break;
- } else {
- break;
- }
- }
- return current;
- } catch (e) {
- console.log("resolveTikTokUrl error:", e);
- return url;
- }
- }
- /**
- * 从完整链接提取 @用户名 与 视频ID
- */
- function parseTikTokUrl(url) {
- let u = String(url || "");
- let usernameMatch = u.match(/@([A-Za-z0-9_.-]+)/);
- let videoMatch = u.match(/\/video\/(\d+)/);
- return {
- url: u,
- username: usernameMatch ? usernameMatch[1] : null,
- videoId: videoMatch ? videoMatch[1] : null
- };
- }
- /**
- * 通过用户名(句柄)获取 TikTok 用户 UID。
- * 依次尝试:官方用户详情 API(带 ttwid)→ node/share 接口 → 解析主页 HTML 兜底。
- * @param {string} username 用户名(可带 @ 前缀)
- * @returns {string|null} uid,获取失败返回 null
- */
- function getTikTokUidByUsername(username) {
- if (!username) return null;
- let handle = String(username).replace(/^@/, "").trim();
- if (!handle) return null;
- // ===== 方式1:官方用户详情 API(带 ttwid Cookie)=====
- let ttwid = fetchTtwidCookie();
- let headers = {
- "Accept": "application/json, text/plain, */*",
- "Referer": "https://www.tiktok.com/@" + encodeURIComponent(handle)
- };
- if (ttwid) headers["Cookie"] = ttwid;
- let obj = httpGetJson(
- "https://www.tiktok.com/api/user/detail/?uniqueId=" + encodeURIComponent(handle),
- headers,
- "getTikTokUidByUsername(api)"
- );
- let uid = obj && obj.userInfo && obj.userInfo.user && obj.userInfo.user.id;
- if (uid) {
- console.log("getTikTokUidByUsername(api): " + handle + " -> " + uid);
- return String(uid);
- }
- // ===== 方式2:node/share 用户接口 =====
- obj = httpGetJson(
- "https://www.tiktok.com/node/share/user/@" + encodeURIComponent(handle)
- + "?uniqueId=" + encodeURIComponent(handle),
- { "Accept": "application/json, text/plain, */*" },
- "getTikTokUidByUsername(node/share)"
- );
- uid = obj && obj.userInfo && obj.userInfo.user && obj.userInfo.user.id;
- if (uid) {
- console.log("getTikTokUidByUsername(node/share): " + handle + " -> " + uid);
- return String(uid);
- }
- // ===== 方式3:解析主页 HTML(__UNIVERSAL_DATA_FOR_REHYDRATION__)=====
- let html = httpGetRaw(
- "https://www.tiktok.com/@" + encodeURIComponent(handle),
- { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" },
- "getTikTokUidByUsername(html)"
- );
- if (html) {
- // 主页 JSON 典型结构: {"id":"123456789","shortId":"...","uniqueId":"handle",...}
- let m = html.match(new RegExp('"id":"(\\d+)","shortId":"[^"]*","uniqueId":"' + escapeRegex(handle) + '"'));
- if (m) {
- console.log("getTikTokUidByUsername(html): " + handle + " -> " + m[1]);
- return String(m[1]);
- }
- // 兜底:定位 uniqueId 字段,向前找最近的 "id"
- let idx = html.indexOf('"uniqueId":"' + handle + '"');
- if (idx !== -1) {
- let seg = html.slice(Math.max(0, idx - 500), idx);
- let m2 = seg.match(/"id":"(\d+)"/);
- if (m2) {
- console.log("getTikTokUidByUsername(html): " + handle + " -> " + m2[1]);
- return String(m2[1]);
- }
- }
- }
- console.log("getTikTokUidByUsername: 获取 " + handle + " 的 UID 失败");
- return null;
- }
- /**
- * 交叉验证 UID 是否真的属于该用户名。
- * 来源1(优先):视频详情页 HTML 中的 author 对象(独立数据源,双重比对 id+uniqueId);
- * 来源2(兜底):主页 HTML 中 uniqueId=handle 的用户对象提取 id 比对,顺带拿 secUid。
- * @param {string} handle 用户名(句柄)
- * @param {string} uid 待验证的 UID
- * @param {string} [videoUrl] 视频链接,用于独立交叉验证
- * @returns {Object} { ok, byProfile, byVideo, secUid }
- * ok=true 表示至少一个来源确认匹配;byProfile/byVideo 取值 true/false/null(无法获取)
- */
- function verifyTikTokUid(handle, uid, videoUrl) {
- let result = { ok: false, byProfile: null, byVideo: null, secUid: null };
- let target = String(uid);
- // ===== 来源1:视频详情页 author 对象(独立交叉)=====
- if (videoUrl) {
- let resolved = resolveTikTokUrl(videoUrl);
- let vHtml = httpGetRaw(resolved, { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }, "verifyTikTokUid(video)");
- if (vHtml) {
- let aidx = vHtml.indexOf('"author":{');
- if (aidx !== -1) {
- let seg = vHtml.slice(aidx, aidx + 2500);
- let mAId = seg.match(/"id":"(\d+)"/);
- let mAUid = seg.match(/"uniqueId":"([^"]+)"/);
- if (mAId) {
- result.byVideo = String(mAId[1]) === target
- && (!mAUid || mAUid[1].toLowerCase() === handle.toLowerCase());
- }
- } else {
- let mAId2 = vHtml.match(/"authorId":"(\d+)"/);
- if (mAId2) result.byVideo = String(mAId2[1]) === target;
- }
- }
- }
- // ===== 来源2:主页 HTML 兜底/补充(顺带拿 secUid)=====
- if (result.byVideo === null) {
- let html = httpGetRaw(
- "https://www.tiktok.com/@" + encodeURIComponent(handle),
- { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" },
- "verifyTikTokUid(profile)"
- );
- if (html) {
- let idx = html.indexOf('"uniqueId":"' + handle + '"');
- if (idx !== -1) {
- let mId = html.slice(Math.max(0, idx - 500), idx).match(/"id":"(\d+)"/);
- if (mId) {
- result.byProfile = String(mId[1]) === target;
- let mSec = html.slice(idx, idx + 3000).match(/"secUid":"([^"]+)"/);
- if (mSec) result.secUid = mSec[1];
- }
- }
- }
- }
- result.ok = result.byProfile === true || result.byVideo === true;
- console.log("verifyTikTokUid: " + handle + " uid=" + target
- + " 主页比对=" + result.byProfile + " 视频页交叉=" + result.byVideo
- + " -> " + (result.ok ? "校验通过" : "校验未通过"));
- return result;
- }
- /**
- * 从视频链接获取作者 UID(支持短链,内部先解析出用户名并交叉验证)
- * @param {string} tiktokUrl 视频链接或短链
- * @returns {string|null} uid;校验不通过或获取失败返回 null
- */
- function getTikTokUidByUrl(tiktokUrl) {
- let resolved = resolveTikTokUrl(tiktokUrl);
- let info = parseTikTokUrl(resolved);
- if (!info.username) {
- console.log("getTikTokUidByUrl: 链接中未解析到用户名: " + resolved);
- return null;
- }
- let uid = getTikTokUidByUsername(info.username);
- if (!uid) return null;
- let check = verifyTikTokUid(info.username, uid, resolved);
- if (check.ok) return uid;
- if (check.byProfile === false || check.byVideo === false) {
- console.log("getTikTokUidByUrl: UID 校验不通过,丢弃结果");
- return null;
- }
- // 两个来源都无法获取时无法交叉验证,仍返回(仅告警)
- console.log("getTikTokUidByUrl: 无法交叉验证,按当前结果返回 uid=" + uid);
- return uid;
- }
- /**
- * 通过 oEmbed 接口获取视频标题与作者昵称。
- * 注意:链接里的 @handle 是账号句柄,页面展示的是昵称,二者可能不同,
- * 所以用 oEmbed 的 author_name 作为页面比对基准最可靠。
- */
- function fetchTikTokOembed(videoUrl) {
- let obj = httpGetJson(
- "https://www.tiktok.com/oembed?url=" + encodeURIComponent(videoUrl),
- { "Accept": "application/json, text/plain, */*" },
- "fetchTikTokOembed"
- );
- if (!obj) return null;
- return {
- title: (obj.title || "").trim(),
- authorName: obj.author_name || "",
- authorUrl: obj.author_url || ""
- };
- }
- /**
- * 在当前页面校验视频是否为目标视频:
- * 1) 作者昵称精确匹配 → 2) 昵称包含匹配 → 3) 视频文案前缀匹配
- * @param {Object} expected { authorName, captionPrefix }
- * @param {number} timeout 轮询超时(ms),默认 8000
- * @returns {boolean} 命中任一特征返回 true
- */
- function verifyTikTokVideoPage(expected, timeout) {
- timeout = timeout || 8000;
- let checks = [];
- if (expected.authorName) {
- checks.push({ key: "作者昵称(精确)", widget: { text: expected.authorName } });
- checks.push({ key: "作者昵称(包含)", widget: { textMatches: "(?s).*" + escapeRegex(expected.authorName) + ".*" } });
- }
- if (expected.captionPrefix) {
- let prefix = String(expected.captionPrefix).slice(0, 12);
- if (prefix) {
- checks.push({ key: "视频文案前缀", widget: { textMatches: "(?s).*" + escapeRegex(prefix) + ".*" } });
- }
- }
- if (checks.length === 0) {
- console.log("verifyTikTokVideoPage: 无可比对的视频特征");
- return false;
- }
- let deadline = new Date().getTime() + timeout;
- while (new Date().getTime() < deadline) {
- for (let i = 0; i < checks.length; i++) {
- if (findWidget(checks[i].widget)) {
- console.log("verifyTikTokVideoPage: 命中【" + checks[i].key + "】");
- return true;
- }
- }
- sleep(800);
- }
- console.log("verifyTikTokVideoPage: 页面未匹配到目标视频特征");
- return false;
- }
- /**
- * 兜底校验(需 root):从 dumpsys activity top 的 Intent 数据中找当前视频ID。
- * @returns {boolean|null} 命中=true / 未命中=false / 无法读取=null
- */
- function verifyByDumpsys(videoId) {
- try {
- let r = shell("dumpsys activity top", true);
- let dump = "";
- if (r && typeof r.result === "string") dump = r.result;
- else if (typeof r === "string") dump = r;
- if (!dump) return null;
- let ids = [];
- let m;
- let re = /\/video\/(\d+)/g;
- while ((m = re.exec(dump)) !== null) {
- ids.push(m[1]);
- }
- if (ids.length === 0) return null;
- let found = ids.indexOf(String(videoId)) !== -1;
- console.log("verifyByDumpsys: 当前页面视频ID=" + ids.join(",") + " 目标=" + videoId + " 命中=" + found);
- return found;
- } catch (e) {
- console.log("verifyByDumpsys error:", e);
- return null;
- }
- }
- /**
- * 打开 TikTok 视频链接并校验是否到达目标视频页
- * @param {string} tiktokUrl TikTok 视频链接(支持短链)
- * @returns {boolean} 确认到达目标视频返回 true,否则 false
- */
- function openTikTokVideo(tiktokUrl) {
- try {
- if (!tiktokUrl) {
- console.log("openTikTokVideo: 缺少视频链接");
- return false;
- }
- // ===== 0. 打开前解析短链,拿到目标视频特征(用户名/视频ID/文案) =====
- let resolvedUrl = resolveTikTokUrl(tiktokUrl);
- let urlInfo = parseTikTokUrl(resolvedUrl);
- let oembed = fetchTikTokOembed(resolvedUrl);
- console.log("目标视频: " + JSON.stringify(urlInfo) + " oembed: " + JSON.stringify(oembed));
- let expected = {
- authorName: (oembed && oembed.authorName) || urlInfo.username || null,
- captionPrefix: (oembed && oembed.title) || null,
- videoId: urlInfo.videoId
- };
- importClass(android.content.Intent);
- importClass(android.net.Uri);
- // 通过 Intent 唤起 TikTok 并打开视频
- let intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(tiktokUrl));
- intent.setPackage("com.zhiliaoapp.musically");
- app.startActivity(intent);
- sleep(random(5000, 6000))
- // 等待页面加载:Follow 按钮与播放按钮
- let resultFollow = text("Follow").id("com.zhiliaoapp.musically:id/sku").findOne(10000)
- let resultPlay = id("com.zhiliaoapp.musically:id/ujp").findOne(2000)
- console.log("resultFollow:", resultFollow)
- console.log("resultPlay:", resultPlay)
- if (resultFollow && resultPlay) {
- clickWidget(resultPlay)
- sleep(5000)
- console.log("到达新视频")
- // ===== 校验当前视频是否为目标视频 =====
- let verified = verifyTikTokVideoPage(expected, 8000);
- if (!verified && expected.videoId) {
- if (verifyByDumpsys(expected.videoId) === true) verified = true;
- }
- console.log("当前视频校验: " + (verified ? "✓ 是目标视频" : "✗ 无法确认是目标视频"));
- return verified;
- }
- console.log("openTikTokVideo: 未找到 Follow 按钮或播放按钮");
- return false;
- } catch (error) {
- console.log("openTikTokVideo error:", error);
- return false;
- }
- }
- // 调用示例
- let tiktokUrl = "https://www.tiktok.com/t/ZTy8r7T2a/";
- console.log("作者UID:", getTikTokUidByUrl(tiktokUrl));
- // openTikTokVideo(tiktokUrl);
|