|
|
@@ -1,6 +1,6 @@
|
|
|
"ui";
|
|
|
/*! tiktok-app v2026.10.13
|
|
|
- * built 2026-10-15T10:12:23.961Z
|
|
|
+ * built 2026-10-17T08:34:01.683Z
|
|
|
* modules: theme, store, records, device, service, engine, session, widgets, dialog, floatball, page-id, page-dm, page-video, page-tasks, page-records, page-login, page-mine, main
|
|
|
* 由 tools/build.js 自动生成 —— 请不要直接编辑本文件,改 src/ 后重新构建。
|
|
|
*/
|
|
|
@@ -441,7 +441,12 @@ var U = Theme.util;
|
|
|
|
|
|
var Store = {};
|
|
|
|
|
|
-Store.SCHEMA_VERSION = 6;
|
|
|
+/* v7(2026-10-17):ID提取页**清语义**(需求重整 A 档)。
|
|
|
+ * 删掉 4 个与卡片同义反复的开关、把 3 处去重收到「功能设置」只出现一次、
|
|
|
+ * 给两个"勾了却无所指"的开关补上它缺的输入、s3 的「进入页面」换成只含粉丝/关注两值的
|
|
|
+ * `expandList`(原来 4 个值里有 2 个在"展开哪个列表"这个语义下是非法值)。
|
|
|
+ * 迁移见 MIGRATIONS[6]。 */
|
|
|
+Store.SCHEMA_VERSION = 7;
|
|
|
Store.STORAGE_NAME = "feizhou_app";
|
|
|
Store.STORAGE_KEY = "config";
|
|
|
|
|
|
@@ -453,6 +458,13 @@ Store.TEXT_DIR = "/sdcard/Download/tiktok_app";
|
|
|
Store.options = {
|
|
|
dedupe: ["ID 模式", "UID 模式"],
|
|
|
enterPage: ["粉丝页", "评论页", "主页", "关注页"],
|
|
|
+ /* 「从哪个页面取」与「展开哪个列表」是**两件事**,v7 之前共用一个 enterPage,
|
|
|
+ * 于是 s3(文本裂变)里能选到「评论页」—— 而在"展开文本里这些人的列表"这个语义下,
|
|
|
+ * 评论页根本不是合法值。分开之后 s3 只能选粉丝 / 关注。 */
|
|
|
+ expandList: ["粉丝列表", "关注列表"],
|
|
|
+ /* 脚本二「视频从哪来」:原来只有一个"自定评论页面提 ID"开关,勾上却没说
|
|
|
+ * 从哪条视频开始 —— 补上这个来源维度,那个开关才有意义。 */
|
|
|
+ videoSource: ["当前屏幕", "关键词搜索"],
|
|
|
aiGender: ["男", "女"],
|
|
|
toggle: ["开启", "关闭"],
|
|
|
dedupeMode: ["关注+私信去重", "仅关注去重", "仅私信去重"],
|
|
|
@@ -475,6 +487,9 @@ Store.enum = {
|
|
|
* 新增候选一律**追加在末尾**:位置即下拉里的顺序,插在中间会让老用户
|
|
|
* 下次点开下拉时发现选项换了地方。 */
|
|
|
enterPage: { "fans": "粉丝页", "comment": "评论页", "home": "主页", "follow": "关注页" },
|
|
|
+ /* v7:只含"能列出一串人的页"。s3 用它,与 enterPage 语义分开。 */
|
|
|
+ expandList: { "fans": "粉丝列表", "follow": "关注列表" },
|
|
|
+ videoSource: { "screen": "当前屏幕", "keyword": "关键词搜索" },
|
|
|
aiGender: { "male": "男", "female": "女" },
|
|
|
toggle: { "on": "开启", "off": "关闭" },
|
|
|
dedupeMode: { "follow_dm": "关注+私信去重", "follow": "仅关注去重", "dm": "仅私信去重" },
|
|
|
@@ -587,17 +602,18 @@ F.push(
|
|
|
* 这一屏(并配了「请先选择要运行的脚本」的拦截弹窗),说明"还没选"是一个
|
|
|
* 真实且合法的初始状态。四个选项里替用户随便挑一个,反而是越权。
|
|
|
* 脚本一默认展开,因为它承载的设置行最多,收起会让人以为它没内容。 */
|
|
|
+/* ——— 脚本一 · 粉丝页提 ID ———
|
|
|
+ * v7 清理:
|
|
|
+ * · 删「自定关注或粉丝页面提 ID」—— 它和下面的「从哪个页面取」说的是同一件事,
|
|
|
+ * 留着只会让用户以为"关掉它"和"取消勾选这张卡"有区别;
|
|
|
+ * · 删「主播去重 + 去重模式」—— 去重收到「功能设置」,全页只出现一次;
|
|
|
+ * · 「自定页面提 ID」补上它缺的关键词输入(原来勾上之后无所指);
|
|
|
+ * · 「进入页面」改叫「从哪个页面取」(它取的是人,不是"进入"某页)。 */
|
|
|
F.push.apply(F, cardFields("tabs.id.scripts.s1", false, true));
|
|
|
F.push(
|
|
|
- /* ★ 2026-10-13 规格书 §3 新增的两项(画布 C1 的脚本一展开态):
|
|
|
- * 「自定关注或粉丝页面提 ID」默认✓、「自定页面提 ID」默认✗。
|
|
|
- * 它们是**取材入口**的两个开关,与「进入页面」下拉不是一回事:
|
|
|
- * 前者决定要不要走自定义入口,后者决定进哪一页。 */
|
|
|
- { path: "tabs.id.scripts.s1.customFansPage", type: "bool", def: true },
|
|
|
- { path: "tabs.id.scripts.s1.customPage", type: "bool", def: false },
|
|
|
- /* 去重是个开关,去重方式才是值 —— 早先只有后者,用户没法把去重整个关掉。 */
|
|
|
- { path: "tabs.id.scripts.s1.dedupeEnabled", type: "bool", def: true },
|
|
|
- { path: "tabs.id.scripts.s1.dedupe", type: "enum", group: "dedupe", def: "id" },
|
|
|
+ /* 搜索用户:原来是个纯开关,勾上却没说搜什么 —— 补关键词。留空 = 没配,optional */
|
|
|
+ { path: "tabs.id.scripts.s1.customPage", type: "bool", def: false },
|
|
|
+ { path: "tabs.id.scripts.s1.customPageKeyword", type: "text", def: "", optional: true },
|
|
|
{ path: "tabs.id.scripts.s1.enterPage", type: "enum", group: "enterPage", def: "fans" },
|
|
|
/* 主播 ID:可填多个,用 # 隔开(设计稿 16:479 的输入提示)。
|
|
|
* 留空 = 不限定主播、从页面现场取 —— 这是真实且合理的状态,所以标 optional,
|
|
|
@@ -607,29 +623,33 @@ F.push(
|
|
|
{ path: "tabs.id.scripts.s1.textIdPath", type: "text", def: Store.TEXT_DIR + "/uid_list.txt" }
|
|
|
);
|
|
|
|
|
|
-/* ——— 脚本二 · 评论页提 ID(设计稿 16:483)——— */
|
|
|
+/* ——— 脚本二 · 评论页提 ID(设计稿 16:483)———
|
|
|
+ * v7:「自定评论页面提 ID」这个纯开关换成了**视频从哪来**。
|
|
|
+ * 原开关勾上只说"用自定义的评论页面",却没说那条视频是谁 —— 无从执行。 */
|
|
|
F.push.apply(F, cardFields("tabs.id.scripts.s2", false, false));
|
|
|
F.push(
|
|
|
- { path: "tabs.id.scripts.s2.customCommentPage", type: "bool", def: true },
|
|
|
- { path: "tabs.id.scripts.s2.commenterFans", type: "bool", def: false }
|
|
|
+ { path: "tabs.id.scripts.s2.videoSource", type: "enum", group: "videoSource", def: "screen" },
|
|
|
+ { path: "tabs.id.scripts.s2.videoKeyword", type: "text", def: "fashion" },
|
|
|
+ { path: "tabs.id.scripts.s2.commenterFans", type: "bool", def: false }
|
|
|
);
|
|
|
|
|
|
-/* ——— 脚本三 · 文本 UID 裂变提 ID(设计稿 16:483)——— */
|
|
|
+/* ——— 脚本三 · 文本 UID 裂变提 ID(设计稿 16:483)———
|
|
|
+ * v7:删「文本 UID 裂变提 ID」(与卡名一字不差,勾不勾都说不通)与「UID 博主去重」
|
|
|
+ * (与脚本一的去重是同一件事,已收到功能设置)。
|
|
|
+ * 「进入页面」换成 expandList —— 这里问的是"展开文本里这些人的哪个列表",
|
|
|
+ * 只有粉丝/关注两个合法答案。 */
|
|
|
F.push.apply(F, cardFields("tabs.id.scripts.s3", false, false));
|
|
|
F.push(
|
|
|
- { path: "tabs.id.scripts.s3.uidFission", type: "bool", def: true },
|
|
|
- { path: "tabs.id.scripts.s3.uidDedupe", type: "bool", def: true },
|
|
|
- /* 脚本三默认从「关注页」进 —— 与脚本一的「粉丝页」不同,这是设计稿的取值 */
|
|
|
- { path: "tabs.id.scripts.s3.enterPage", type: "enum", group: "enterPage", def: "follow" },
|
|
|
+ { path: "tabs.id.scripts.s3.enterPage", type: "enum", group: "expandList", def: "follow" },
|
|
|
{ path: "tabs.id.scripts.s3.fissionTextPath", type: "text", def: Store.TEXT_DIR + "/uid_list.txt" }
|
|
|
);
|
|
|
|
|
|
/* ——— 脚本四 · 关键词视频提 ID(设计稿 16:589)———
|
|
|
+ * v7:删「关键词视频提 ID」(与卡名一字不差)。
|
|
|
* 「关键词」给的是可直接跑的业务默认值而不是空串:空着的输入框既看不出该填什么,
|
|
|
* 也让「未配置」和「配了空值」无法区分(与视频功能页的关键词同一套规则)。 */
|
|
|
F.push.apply(F, cardFields("tabs.id.scripts.s4", false, false));
|
|
|
F.push(
|
|
|
- { path: "tabs.id.scripts.s4.keywordEnabled", type: "bool", def: true },
|
|
|
{ path: "tabs.id.scripts.s4.keyword", type: "text", def: "fashion" },
|
|
|
/* 三个「提取…」是额外的取材范围,默认都关:默认行为只剩「提视频作者的 ID」这一件事,
|
|
|
* 最可预期。要更多来源自己勾 —— 反过来(默认全开)会让人不知道它到底抓了什么。 */
|
|
|
@@ -652,6 +672,11 @@ F.push(
|
|
|
* 「还没填表名」是一个真实且合理的状态,不该被塞一个假默认值。
|
|
|
* 取值审计靠这个标记把它和「漏了默认值」区分开。 */
|
|
|
{ path: "tabs.id.features.cloudTable", type: "text", def: "", optional: true },
|
|
|
+ /* v7:本地去重从脚本卡收到这里,全页只出现一次。
|
|
|
+ * 「跨轮去重」是另一件事:与历史已提取的 ID 比对,所以单独一个开关 ——
|
|
|
+ * 原来用「ID 模式 / UID 模式」两个词表达它,用户根本看不出那是在说"要不要跟历史比"。 */
|
|
|
+ { path: "tabs.id.features.dedupeLocal", type: "bool", def: true },
|
|
|
+ { path: "tabs.id.features.dedupeHistory", type: "bool", def: false },
|
|
|
{ path: "tabs.id.features.aiAvatar", type: "bool", def: false },
|
|
|
{ path: "tabs.id.features.aiGender", type: "enum", group: "aiGender", def: "male" },
|
|
|
{ path: "tabs.id.features.aiAgeMax", type: "int", min: 0, max: 100, def: 20 },
|
|
|
@@ -902,6 +927,45 @@ Store.MIGRATIONS = {
|
|
|
} else if (oldTab === "mine") {
|
|
|
assignPath(cfg, "activeTab", "mine");
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * v6 → v7:ID提取页清语义(需求重整 A 档)。
|
|
|
+ *
|
|
|
+ * 两件必须做的,都是"类型系统看不出来的语义差异":
|
|
|
+ *
|
|
|
+ * ① **s3 的「进入页面」值域收窄**。原来是共用 `enterPage` 的四个值
|
|
|
+ * (粉丝页/评论页/主页/关注页),但这个字段在脚本三里的意思是
|
|
|
+ * "展开文本里这些人的哪个**列表**" —— 评论页和主页根本不是列表。
|
|
|
+ * 存量值是这两个的,一律落到「关注列表」(脚本三的设计稿默认值)。
|
|
|
+ * 不处理的话,`normalizeField` 会把非法枚举悄悄退回默认值,
|
|
|
+ * 用户只会看到"我选过的选项自己变了",而不知道发生了什么。
|
|
|
+ *
|
|
|
+ * ② **去重从卡片搬到功能设置**。旧的 `dedupeEnabled` 是用户对"要不要去重"
|
|
|
+ * 的真实表达,不能因为控件搬家就丢掉 —— 把它继承到 `dedupeLocal`。
|
|
|
+ * 旧 `dedupe`(ID 模式 / UID 模式)表达的其实是"跟不跟历史比",
|
|
|
+ * 映射到新增的 `dedupeHistory`("UID 模式"= 按输入文本比 = 留了个文本基准,
|
|
|
+ * 语义上更接近"跨轮")。这条映射**不完美**,所以写进 problems 里说清楚。
|
|
|
+ */
|
|
|
+ 6: function (cfg, problems, src) {
|
|
|
+ var oldEnter = readPath(src, "tabs.id.scripts.s3.enterPage");
|
|
|
+ if (oldEnter === "comment" || oldEnter === "home") {
|
|
|
+ assignPath(cfg, "tabs.id.scripts.s3.enterPage", "follow");
|
|
|
+ problems.push("脚本三「进入页面」= " + oldEnter +
|
|
|
+ " 在「展开哪个列表」的语义下不成立,已改为「关注列表」");
|
|
|
+ }
|
|
|
+
|
|
|
+ var oldDedupe = readPath(src, "tabs.id.scripts.s1.dedupeEnabled");
|
|
|
+ if (typeof oldDedupe === "boolean") {
|
|
|
+ assignPath(cfg, "tabs.id.features.dedupeLocal", oldDedupe);
|
|
|
+ problems.push("「主播去重」已并入功能设置 →「本地去重」,旧值 " + oldDedupe + " 已沿用");
|
|
|
+ }
|
|
|
+
|
|
|
+ var oldMode = readPath(src, "tabs.id.scripts.s1.dedupe");
|
|
|
+ if (oldMode === "text") {
|
|
|
+ assignPath(cfg, "tabs.id.features.dedupeHistory", true);
|
|
|
+ problems.push("旧去重模式「UID 模式」→「跨轮去重」已打开(按输入文本比对历史)");
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -2819,9 +2883,19 @@ W.settingRow = function (label, rightView, gapDp) {
|
|
|
*
|
|
|
* 命中区含整个标签:只让 16dp 的方框可点,真机上很难戳中。
|
|
|
*/
|
|
|
-W.checkRow = function (label, checked, onToggle) {
|
|
|
+/**
|
|
|
+ * 勾选行。
|
|
|
+ *
|
|
|
+ * @param disabled 置灰且不可点。**为「功能还没做,但字段要保留」的场景准备的**:
|
|
|
+ * 给了界面一个"开得起来、却什么也不会发生"的开关,比不给更糟
|
|
|
+ * (用户会以为它生效了)。置灰至少是诚实的。
|
|
|
+ * 勾选状态**照常显示**——它代表"用户上次的选择",不是"现在能改的选择"。
|
|
|
+ */
|
|
|
+W.checkRow = function (label, checked, onToggle, disabled) {
|
|
|
var box = W.checkbox(checked, 16);
|
|
|
- var labelView = U.text(label, Theme.m.labelSize, Theme.hex.textSecondary, false);
|
|
|
+ var off = !!disabled;
|
|
|
+ var labelView = U.text(label, Theme.m.labelSize,
|
|
|
+ off ? Theme.hex.textMuted : Theme.hex.textSecondary, false);
|
|
|
labelView.setSingleLine(true);
|
|
|
|
|
|
var line = U.row(false);
|
|
|
@@ -2830,15 +2904,60 @@ W.checkRow = function (label, checked, onToggle) {
|
|
|
line.addView(labelView);
|
|
|
line.setPadding(0, U.dp(4), 0, U.dp(4));
|
|
|
|
|
|
- line.setOnClickListener(function () {
|
|
|
- var next = !box.isChecked();
|
|
|
- box.set(next);
|
|
|
- onToggle(next);
|
|
|
- });
|
|
|
+ if (off) {
|
|
|
+ line.setAlpha(0.55);
|
|
|
+ /* 不可点:这里挂一个**空监听**而不是不挂 —— 不挂的话父容器(卡片)
|
|
|
+ * 的点击会继续冒泡上去,用户点这一行会触发别处的动作。
|
|
|
+ * 注意 setOnClickListener(function(){}) 与 setOnClickListener(null) 行为不同:
|
|
|
+ * 后者等于"没有监听器",照样冒泡(见 README 铁律)。 */
|
|
|
+ line.setOnClickListener(function () { });
|
|
|
+ } else {
|
|
|
+ line.setOnClickListener(function () {
|
|
|
+ var next = !box.isChecked();
|
|
|
+ box.set(next);
|
|
|
+ onToggle(next);
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
return { root: line, isChecked: box.isChecked };
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * 灰行:**只读展示 + 「暂不支持」标记**。
|
|
|
+ *
|
|
|
+ * 专门给"设置项在界面上占着位置、但执行侧还没有能力实现"的项用
|
|
|
+ * (提取区域 / AI 头像识别 / AI 性别 / AI 年龄)。
|
|
|
+ *
|
|
|
+ * 为什么不是"灰掉的勾选框":勾选框本身就在说"这个可以改",
|
|
|
+ * 灰只是说"现在不能改",两者叠起来仍然在暗示它是个可选项。
|
|
|
+ * 而这里的事实是"它现在根本没有效果" —— 所以不给可操作控件,
|
|
|
+ * 只把用户上次填的值显示出来(保留值、将来接上就能用),点一行看原因。
|
|
|
+ */
|
|
|
+W.mutedRow = function (label, valueText, onTap) {
|
|
|
+ var line = U.row(false);
|
|
|
+ var labelView = U.text(label, Theme.m.labelSize, Theme.hex.textMuted, false);
|
|
|
+ labelView.setSingleLine(true);
|
|
|
+ line.addView(labelView);
|
|
|
+ line.addView(U.flexible());
|
|
|
+
|
|
|
+ if (valueText) {
|
|
|
+ var valueView = U.text(valueText, Theme.m.chipSize, Theme.hex.textFaint, false);
|
|
|
+ valueView.setSingleLine(true);
|
|
|
+ line.addView(valueView);
|
|
|
+ line.addView(U.hgap(6));
|
|
|
+ }
|
|
|
+
|
|
|
+ line.addView(W.chip("暂不支持", "mute").root);
|
|
|
+ line.setPadding(0, U.dp(4), 0, U.dp(4));
|
|
|
+
|
|
|
+ if (onTap) {
|
|
|
+ line.setOnClickListener(onTap);
|
|
|
+ } else {
|
|
|
+ line.setOnClickListener(function () { });
|
|
|
+ }
|
|
|
+ return { root: line };
|
|
|
+};
|
|
|
+
|
|
|
/* ------------------------------------------------------------ 胶囊 / 标签 */
|
|
|
|
|
|
/* 胶囊的四套色调集中在这里,W.chip 与 W.repaintChip 共用,
|
|
|
@@ -5661,12 +5780,15 @@ Pages.registerScripts = function (tab, list) {
|
|
|
Pages.SCRIPTS_OF[tab] = list;
|
|
|
};
|
|
|
|
|
|
-/* 四张脚本卡的文案与摘要。摘要只在折叠态显示,展开后由具体设置行替代。 */
|
|
|
+/* 四张脚本卡的文案与摘要。摘要只在折叠态显示,展开后由具体设置行替代。
|
|
|
+ * v7 顺手把 s2 的摘要从「评论裂变」改成「评论者」——
|
|
|
+ * "裂变"这个词在脚本二里其实只表示"再取评论者的粉丝",
|
|
|
+ * 而它在脚本三里又是另一种意思,同一个词两种含义最容易读糊。 */
|
|
|
Pages.ID_SCRIPTS = [
|
|
|
- { key: "s1", name: "\u811A\u672C\u4E00 \u00B7 \u7C89\u4E1D\u9875\u63D0 ID" },
|
|
|
- { key: "s2", name: "\u811A\u672C\u4E8C \u00B7 \u8BC4\u8BBA\u9875\u63D0 ID", summary: "\u8BC4\u8BBA\u88C2\u53D8" },
|
|
|
- { key: "s3", name: "\u811A\u672C\u4E09 \u00B7 \u6587\u672C UID \u88C2\u53D8\u63D0 ID", summary: "\u6587\u672C UID" },
|
|
|
- { key: "s4", name: "\u811A\u672C\u56DB \u00B7 \u5173\u952E\u8BCD\u89C6\u9891\u63D0 ID", summary: "\u5173\u952E\u8BCD\u89C6\u9891" }
|
|
|
+ { key: "s1", name: "脚本一 · 粉丝页提 ID" },
|
|
|
+ { key: "s2", name: "脚本二 · 评论页提 ID", summary: "评论者" },
|
|
|
+ { key: "s3", name: "脚本三 · 文本 UID 裂变提 ID", summary: "文本 UID" },
|
|
|
+ { key: "s4", name: "脚本四 · 关键词视频提 ID", summary: "关键词视频" }
|
|
|
];
|
|
|
|
|
|
Pages.registerScripts("id", Pages.ID_SCRIPTS);
|
|
|
@@ -5739,168 +5861,194 @@ function buildToggleRow(opts) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 脚本一 · 粉丝页提 ID(设计稿 3:244,2026-10-13 按新稿补齐两项)。
|
|
|
- *
|
|
|
- * 七项:自定关注或粉丝页面提 ID(✓)+ 自定页面提 ID(✗)→ 主播去重(开关 + 去重模式)
|
|
|
- * → 进入页面 → 主播 ID → 使用文本 ID(开关 + 路径 + 选择)。
|
|
|
+ * 脚本一 · 粉丝页提 ID(设计稿 3:244;v7 清语义)。
|
|
|
*
|
|
|
- * 前两项是**取材入口**的开关,与「进入页面」下拉不是一回事:
|
|
|
- * 前者决定"要不要走自定义入口",后者决定"进哪一页"。
|
|
|
- * 稿子把它们排在主播去重之前,顺序照抄 —— 取材范围先于去重策略。
|
|
|
+ * v7 改了三处,都是"让每个控件只有一种含义":
|
|
|
+ * · 删「自定关注或粉丝页面提 ID」—— 它与下面的「从哪个页面取」指的是同一件事,
|
|
|
+ * 两个控件表达一个意思,用户只能猜"关掉它"和"取消勾选这张卡"有什么区别;
|
|
|
+ * · 删「主播去重 + 去重模式」—— 去重收到「功能设置」,全页只出现一次;
|
|
|
+ * · 「自定页面提 ID」补上关键词输入 —— 原来勾上之后没说搜什么,无从执行。
|
|
|
*
|
|
|
- * 「主播去重」和「进入页面」在稿子里同属主播去重那一组的两个下拉,
|
|
|
- * 这里仍拆成两行:一个开关只关自己的下拉,否则用户分不清
|
|
|
- * 「去重方式 = ID 模式」到底属于哪个设置。
|
|
|
+ * 顺序改成「主路径 → 额外来源」:先把"从哪个页面取 + 主播账号"放一起,
|
|
|
+ * 这两项合起来才构成一次完整取材;搜索用户与文本是两个并列的额外入口。
|
|
|
*/
|
|
|
function buildIdScript1Detail() {
|
|
|
var base = "tabs.id.scripts.s1.";
|
|
|
var rows = [];
|
|
|
|
|
|
- rows.push(W.checkRow("\u81EA\u5B9A\u5173\u6CE8\u6216\u7C89\u4E1D\u9875\u9762\u63D0 ID",
|
|
|
- Store.getBool(base + "customFansPage"),
|
|
|
- function (on) { Store.set(base + "customFansPage", on); }).root);
|
|
|
-
|
|
|
- rows.push(W.checkRow("\u81EA\u5B9A\u9875\u9762\u63D0 ID",
|
|
|
- Store.getBool(base + "customPage"),
|
|
|
- function (on) { Store.set(base + "customPage", on); }).root);
|
|
|
-
|
|
|
- rows.push(buildToggleRow({
|
|
|
- path: base + "dedupeEnabled",
|
|
|
- label: "\u4E3B\u64AD\u53BB\u91CD",
|
|
|
- rerender: true,
|
|
|
- rightOf: function () { return W.dropdown("dedupe", base + "dedupe").root; }
|
|
|
- }));
|
|
|
-
|
|
|
- rows.push(W.settingRow("\u8FDB\u5165\u9875\u9762",
|
|
|
+ /* —— 主路径:主播账号 → 它的哪个列表 —— */
|
|
|
+ rows.push(W.settingRow("从哪个页面取",
|
|
|
W.dropdown("enterPage", base + "enterPage").root, 8));
|
|
|
|
|
|
- /* 主播 ID:可填多个,用 # 隔开;留空表示不限定、从页面现场取 */
|
|
|
- rows.push(W.settingRow("\u4E3B\u64AD ID",
|
|
|
+ /* 主播账号:可填多个,用 # 隔开;留空表示不限定、从当前屏幕的列表直接抓 */
|
|
|
+ rows.push(W.settingRow("主播账号",
|
|
|
W.inlineField({
|
|
|
path: base + "anchorIds",
|
|
|
- placeholder: "ID1#ID2#ID3",
|
|
|
+ placeholder: "账号1#账号2#账号3",
|
|
|
widthDp: 148
|
|
|
}).root, 8));
|
|
|
|
|
|
- /* 使用文本 ID:勾上后从本地文本读,不再现场抽取。
|
|
|
- * 这一行的右侧是「路径输入框 + 选择按钮」,比其它行多一个控件,
|
|
|
- * 所以不用 buildToggleRow(它只接受一个右侧控件)。 */
|
|
|
- var useBox = W.checkRow("\u4F7F\u7528\u6587\u672C ID",
|
|
|
+ rows.push(W.hint("留空 = 从当前屏幕上的列表直接抓", false));
|
|
|
+
|
|
|
+ /* —— 额外来源 1:搜索用户(补上缺的关键词,否则勾了也不知道搜什么) —— */
|
|
|
+ var pageBox = W.checkRow("搜索用户提 ID",
|
|
|
+ Store.getBool(base + "customPage"),
|
|
|
+ function (on) {
|
|
|
+ Store.set(base + "customPage", on);
|
|
|
+ /* 勾上之后右侧要多出关键词输入框,只能重建这一页 */
|
|
|
+ Pages.requestRerender();
|
|
|
+ });
|
|
|
+ var pageRow = U.row();
|
|
|
+ pageRow.addView(pageBox.root);
|
|
|
+ if (Store.getBool(base + "customPage")) {
|
|
|
+ pageRow.addView(U.flexible());
|
|
|
+ pageRow.addView(U.hgap(8));
|
|
|
+ pageRow.addView(W.inlineField({
|
|
|
+ path: base + "customPageKeyword",
|
|
|
+ placeholder: "搜索关键词",
|
|
|
+ widthDp: 100,
|
|
|
+ align: "right"
|
|
|
+ }).root);
|
|
|
+ }
|
|
|
+ rows.push(pageRow);
|
|
|
+
|
|
|
+ /* —— 额外来源 2:本地文本 —— */
|
|
|
+ var useBox = W.checkRow("使用文本 ID",
|
|
|
Store.getBool(base + "useTextId"),
|
|
|
function (on) {
|
|
|
Store.set(base + "useTextId", on);
|
|
|
- /* 勾上之后右侧要多出「选择文件」,只能重建这一页 */
|
|
|
+ /* 勾上之后右侧要多出「路径 + 选择文件」,只能重建这一页 */
|
|
|
Pages.requestRerender();
|
|
|
});
|
|
|
|
|
|
/* 路径本身就可在栏位里直接改(可以手输,不一定非走「选择文件」) */
|
|
|
var pathField = W.inlineField({
|
|
|
path: base + "textIdPath",
|
|
|
- placeholder: "\u6587\u672C\u8DEF\u5F84",
|
|
|
+ placeholder: "文本路径",
|
|
|
widthDp: 110,
|
|
|
align: "right"
|
|
|
});
|
|
|
|
|
|
var useRow = U.row();
|
|
|
useRow.addView(useBox.root);
|
|
|
- useRow.addView(U.flexible());
|
|
|
- useRow.addView(pathField.root);
|
|
|
if (Store.getBool(base + "useTextId")) {
|
|
|
+ useRow.addView(U.flexible());
|
|
|
+ useRow.addView(pathField.root);
|
|
|
useRow.addView(U.hgap(6));
|
|
|
useRow.addView(W.filePickerButton(base + "textIdPath", pathField.paint).root);
|
|
|
}
|
|
|
rows.push(useRow);
|
|
|
|
|
|
+ rows.push(W.hint("以上两项是额外的取材来源,可与主播账号同时使用", false));
|
|
|
+
|
|
|
var detail = U.column();
|
|
|
W.stack(detail, rows, 12);
|
|
|
return detail;
|
|
|
}
|
|
|
|
|
|
-/** 脚本二 · 评论页提 ID(设计稿 16:483)。两项都是开关,都改变「去哪取材」。 */
|
|
|
+/**
|
|
|
+ * 脚本二 · 评论页提 ID(设计稿 16:483;v7 换掉一个纯开关)。
|
|
|
+ *
|
|
|
+ * v7:「自定评论页面提 ID」勾上只说"用自定义的评论页面",却没说**是哪条视频的**
|
|
|
+ * —— 执行时无从下手。换成一个真正决定取材起点的维度:视频从哪来。
|
|
|
+ * ("当前屏幕"= 用户已经自己停在某个评论区,这是最常见也最省事的用法。)
|
|
|
+ */
|
|
|
function buildIdScript2Detail() {
|
|
|
var base = "tabs.id.scripts.s2.";
|
|
|
- var rows = [
|
|
|
- buildToggleRow({
|
|
|
- path: base + "customCommentPage",
|
|
|
- label: "\u81EA\u5B9A\u8BC4\u8BBA\u9875\u9762\u63D0 ID",
|
|
|
- rerender: false
|
|
|
- }),
|
|
|
- buildToggleRow({
|
|
|
- path: base + "commenterFans",
|
|
|
- label: "\u63D0\u53D6\u8BC4\u8BBA\u7684\u4EBA\u7684\u7C89\u4E1D ID",
|
|
|
- rerender: false
|
|
|
- })
|
|
|
- ];
|
|
|
+ var rows = [];
|
|
|
+
|
|
|
+ rows.push(W.settingRow("视频从哪来",
|
|
|
+ W.dropdown("videoSource", base + "videoSource", function () {
|
|
|
+ /* 选「关键词搜索」才需要关键词输入,得重建这一页 */
|
|
|
+ Pages.requestRerender();
|
|
|
+ }).root, 8));
|
|
|
+
|
|
|
+ if (Store.get("tabs.id.scripts.s2.videoSource") === "keyword") {
|
|
|
+ rows.push(W.settingRow("搜索关键词",
|
|
|
+ W.inlineField({
|
|
|
+ path: base + "videoKeyword",
|
|
|
+ placeholder: "关键词",
|
|
|
+ widthDp: 148
|
|
|
+ }).root, 8));
|
|
|
+ } else {
|
|
|
+ rows.push(W.hint("当前屏幕 = 用你此刻停在的那条视频的评论区", false));
|
|
|
+ }
|
|
|
+
|
|
|
+ rows.push(buildToggleRow({
|
|
|
+ path: base + "commenterFans",
|
|
|
+ label: "再取评论者的粉丝 ID",
|
|
|
+ rerender: false
|
|
|
+ }));
|
|
|
+
|
|
|
var detail = U.column();
|
|
|
W.stack(detail, rows, 12);
|
|
|
return detail;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 脚本三 · 文本 UID 裂变提 ID(设计稿 16:483)。
|
|
|
+ * 脚本三 · 文本 UID 裂变提 ID(设计稿 16:483;v7 删两个开关)。
|
|
|
*
|
|
|
- * 「裂变文本」用的是 `W.filePicker` —— 设计稿那一行就是「标签 + 路径 + 选择」,
|
|
|
- * 与 UID私信页的「随机私信内容」完全同形,没必要再写一个。
|
|
|
+ * v7:
|
|
|
+ * · 删「文本 UID 裂变提 ID」—— 与卡片名一字不差,勾不勾都说不通;
|
|
|
+ * · 删「UID 博主去重」—— 与脚本一的去重是同一件事,已收到「功能设置」;
|
|
|
+ * · 「进入页面」→「展开哪个列表」,并换成只含粉丝/关注两个值的 `expandList`
|
|
|
+ * (原来那套 4 个值里有「评论页」「主页」,在"展开一个列表"的语义下根本不存在)。
|
|
|
*/
|
|
|
function buildIdScript3Detail() {
|
|
|
var base = "tabs.id.scripts.s3.";
|
|
|
- var rows = [
|
|
|
- buildToggleRow({
|
|
|
- path: base + "uidFission",
|
|
|
- label: "\u6587\u672C UID \u88C2\u53D8\u63D0 ID",
|
|
|
- rerender: false
|
|
|
- }),
|
|
|
- buildToggleRow({
|
|
|
- path: base + "uidDedupe",
|
|
|
- label: "UID \u535A\u4E3B\u53BB\u91CD",
|
|
|
- rerender: false
|
|
|
- }),
|
|
|
- W.settingRow("\u8FDB\u5165\u9875\u9762",
|
|
|
- W.dropdown("enterPage", base + "enterPage").root, 8),
|
|
|
- W.filePicker("\u88C2\u53D8\u6587\u672C", base + "fissionTextPath", "\u9009\u62E9").root
|
|
|
- ];
|
|
|
+ var rows = [];
|
|
|
+
|
|
|
+ rows.push(W.settingRow("展开哪个列表",
|
|
|
+ W.dropdown("expandList", base + "enterPage").root, 8));
|
|
|
+
|
|
|
+ rows.push(W.filePicker("裂变文本", base + "fissionTextPath", "选择").root);
|
|
|
+
|
|
|
+ rows.push(W.hint("对文本里的每个 UID,取它上面选的那个列表", false));
|
|
|
+
|
|
|
var detail = U.column();
|
|
|
W.stack(detail, rows, 12);
|
|
|
return detail;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 脚本四 · 关键词视频提 ID(设计稿 16:589)。
|
|
|
+ * 脚本四 · 关键词视频提 ID(设计稿 16:589;v7 删一个开关 + 补层级)。
|
|
|
*
|
|
|
- * 一行关键词 + 四个开关。四个开关是「取材范围」的层层扩大:
|
|
|
- * 视频作者 → 作者的粉丝 → 评论区的人 → 评论人的粉丝。
|
|
|
- * 层层嵌套的措辞容易看糊,所以顺序按「从近到远」排,与设计稿一致。
|
|
|
+ * v7:
|
|
|
+ * · 删「关键词视频提 ID」(与卡名一字不差);
|
|
|
+ * · 三个「提取…」原来是四项平级排列,读不出"第 1 项是根、后 3 项是扩展",
|
|
|
+ * 更读不出"评论区与作者粉丝是同一层、评论者的粉丝更深一层" ——
|
|
|
+ * 现在加缩进 + 一句说明,让层级在视觉上成立。
|
|
|
*/
|
|
|
function buildIdScript4Detail() {
|
|
|
var base = "tabs.id.scripts.s4.";
|
|
|
- var rows = [
|
|
|
- buildToggleRow({
|
|
|
- path: base + "keywordEnabled",
|
|
|
- label: "\u5173\u952E\u8BCD\u89C6\u9891\u63D0 ID",
|
|
|
- rerender: false
|
|
|
- }),
|
|
|
- W.settingRow("\u5173\u952E\u8BCD\u4E3B\u64AD",
|
|
|
- W.inlineField({
|
|
|
- path: base + "keyword",
|
|
|
- placeholder: "\u5173\u952E\u8BCD",
|
|
|
- widthDp: 148
|
|
|
- }).root, 8),
|
|
|
- buildToggleRow({
|
|
|
- path: base + "anchorFans",
|
|
|
- label: "\u5173\u952E\u8BCD\u63D0\u53D6\u4E3B\u64AD\u7C89\u4E1D ID",
|
|
|
- rerender: false
|
|
|
- }),
|
|
|
- buildToggleRow({
|
|
|
- path: base + "commentArea",
|
|
|
- label: "\u5173\u952E\u8BCD\u63D0\u53D6\u8BC4\u8BBA\u533A ID",
|
|
|
- rerender: false
|
|
|
- }),
|
|
|
- buildToggleRow({
|
|
|
- path: base + "commenterFans",
|
|
|
- label: "\u5173\u952E\u8BCD\u63D0\u53D6\u8BC4\u8BBA\u7684\u4EBA\u7684\u7C89\u4E1D ID",
|
|
|
- rerender: false
|
|
|
- })
|
|
|
+ var rows = [];
|
|
|
+
|
|
|
+ rows.push(W.settingRow("关键词",
|
|
|
+ W.inlineField({
|
|
|
+ path: base + "keyword",
|
|
|
+ placeholder: "关键词",
|
|
|
+ widthDp: 148
|
|
|
+ }).root, 8));
|
|
|
+
|
|
|
+ rows.push(W.hint("默认只提取视频作者的 ID;下面三项是额外的取材范围", false));
|
|
|
+
|
|
|
+ var extras = [
|
|
|
+ { path: "anchorFans", label: "作者的粉丝 ID" },
|
|
|
+ { path: "commentArea", label: "评论区里的 ID" },
|
|
|
+ { path: "commenterFans", label: "评论者的粉丝 ID" }
|
|
|
];
|
|
|
+ for (var i = 0; i < extras.length; i++) {
|
|
|
+ var row = buildToggleRow({
|
|
|
+ path: base + extras[i].path,
|
|
|
+ label: extras[i].label,
|
|
|
+ rerender: false
|
|
|
+ });
|
|
|
+ /* 缩进表达层级:这三项是"在作者之外再取",与上面的关键词不是平级。
|
|
|
+ * 用左内边距而不是加 "└" 之类的符号 —— 符号要占字宽,标签一长就会被挤。 */
|
|
|
+ row.setPadding(U.dp(16), U.dp(4), 0, U.dp(4));
|
|
|
+ rows.push(row);
|
|
|
+ }
|
|
|
+
|
|
|
var detail = U.column();
|
|
|
W.stack(detail, rows, 12);
|
|
|
return detail;
|
|
|
@@ -5961,72 +6109,129 @@ function buildIdScriptCard(script) {
|
|
|
|
|
|
/* ------------------------------------------------------------ 功能设置 */
|
|
|
|
|
|
+/** 卡内分组标题。
|
|
|
+ * 比 W.sectionTitle 轻一档:功能设置是**一张卡里的三个块**,不是三个页面段,
|
|
|
+ * 用页面级标题会把三组切成三个互不相干的区域。 */
|
|
|
+function featureGroup(title) {
|
|
|
+ var view = U.text(title, 11, Theme.hex.textMuted, true);
|
|
|
+ view.setPadding(0, U.dp(8), 0, 0);
|
|
|
+ return view;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
- * 功能设置卡(设计稿 16:733)。
|
|
|
+ * 功能设置卡(设计稿 16:733;v7 重排为三组)。
|
|
|
*
|
|
|
- * 说明两处刻意的处理:
|
|
|
- * · 「提取区域」的输入提示与两行说明都保留,因为国家简码的写法(# 分隔)
|
|
|
- * 不看说明是猜不出来的;
|
|
|
- * · 关注量 / 粉丝量 / 获赞量 / 视频量用 `W.rangeRow` ——
|
|
|
- * 「最小值 ~ 最大值」一个区间占一行,但**两个数各自一个胶囊、各自输入**。
|
|
|
- * 这与 UID私信页的「关注上限 / 私信上限」并不冲突:那边是两个**各自独立成立**的量,
|
|
|
- * 该分开;这里的 min/max 是**同一个量的下界与上界**,拆两行反而像两个不相干的设置。
|
|
|
- * 一行放下也让这张卡短了一半,四个门槛不用滚很久才看完。
|
|
|
+ * v7 做了三件事:
|
|
|
+ * ① **分三组**:原来 19 个字段平铺,混了"过滤条件 / 检测方式 / 待支持项"三种性质,
|
|
|
+ * 用户读不出哪些会真的丢人。现在按「只要这样的账号 / 数值门槛 / 去重与输出」分组。
|
|
|
+ * ② **置灰两项**:提取区域、AI 画像(头像·性别·年龄)当前没有承载者 ——
|
|
|
+ * 不再给"能开却无效"的控件,改成灰行 + 「暂不支持」标记,点一行看原因。
|
|
|
+ * 值仍保留在配置里,将来接上直接生效。
|
|
|
+ * ③ **去重收口**:脚本卡里的「主播去重 + 去重模式」「UID 博主去重」全部搬到这里,
|
|
|
+ * 用「本地去重 / 跨轮去重」两个说人话的开关表达(全页只出现一次)。
|
|
|
+ *
|
|
|
+ * 关注量/粉丝量/获赞量/视频量仍用 `W.rangeRow` —— 「最小值 ~ 最大值」一个区间占一行,
|
|
|
+ * 但两个数各自一个胶囊、各自输入。与 UID私信页的「关注上限 / 私信上限」不冲突:
|
|
|
+ * 那边是两个**各自独立成立**的量,该分开;这里的 min/max 是**同一个量的下界与上界**,
|
|
|
+ * 拆两行反而像两个不相干的设置。
|
|
|
*/
|
|
|
function buildIdFeatures() {
|
|
|
var card = W.card(false);
|
|
|
var base = "tabs.id.features.";
|
|
|
- var rows = [];
|
|
|
+ var blocks = [];
|
|
|
|
|
|
- /* —— 过滤账户:设计稿里是两个勾选框并排 —— */
|
|
|
- var privateRow = W.checkRow("\u8FC7\u6EE4\u79C1\u5BC6\u8D26\u6237",
|
|
|
- Store.getBool(base + "filterPrivate"),
|
|
|
+ /* ══════════════════ 组 1 · 只要这样的账号 ══════════════════ */
|
|
|
+ blocks.push(featureGroup("只要这样的账号"));
|
|
|
+
|
|
|
+ /* 过滤私密 / 无头像:两个勾选框并排(设计稿 16:743) */
|
|
|
+ var privateRow = W.checkRow("过滤私密账户", Store.getBool(base + "filterPrivate"),
|
|
|
function (on) { Store.set(base + "filterPrivate", on); });
|
|
|
- var noAvatarRow = W.checkRow("\u8FC7\u6EE4\u65E0\u5934\u50CF\u8D26\u6237",
|
|
|
- Store.getBool(base + "filterNoAvatar"),
|
|
|
+ var noAvatarRow = W.checkRow("过滤无头像账户", Store.getBool(base + "filterNoAvatar"),
|
|
|
function (on) { Store.set(base + "filterNoAvatar", on); });
|
|
|
var filterLine = U.row();
|
|
|
+ /* weight=1 在这里是安全的:两个控件同源、都只是"勾选框+短标签",
|
|
|
+ * 不会出现一边 MATCH_PARENT 把另一边挤成 0 宽的情况。 */
|
|
|
U.params(privateRow.root, 0, -2, 1);
|
|
|
U.params(noAvatarRow.root, 0, -2, 1);
|
|
|
filterLine.addView(privateRow.root);
|
|
|
filterLine.addView(noAvatarRow.root);
|
|
|
- rows.push(filterLine);
|
|
|
-
|
|
|
- /* —— 提取区域:国家简码 + 条款按钮在同一行(设计稿 16:743) —— */
|
|
|
- var regionNote = W.chip("\u542B\u5BF9\u5E94\u533A\u57DFID\u6761\u6B3E", "accent");
|
|
|
- regionNote.root.setOnClickListener(function () {
|
|
|
- Dialog.alert("\u533A\u57DF ID \u6761\u6B3E",
|
|
|
- "\u52FE\u9009\u540E\uFF0C\u53EA\u63D0\u53D6\u7B26\u5408\u4E0A\u65B9\u56FD\u5BB6\u7B80\u7801\u7684\u8D26\u53F7\u3002\n\n" +
|
|
|
- "\u56FD\u5BB6\u7B80\u7801\uFF1A\u5982\u65E5\u672C\u586B JP\uFF1B\u4E2D\u56FD\u53F0\u6E7E\uFF1ATW\uFF1B" +
|
|
|
- "\u591A\u56FD\u7528 # \u9694\u5F00\u3002\n\u7559\u7A7A\u5219\u5339\u914D\u6240\u6709\u56FD\u5BB6\u3002");
|
|
|
- });
|
|
|
+ blocks.push(filterLine);
|
|
|
|
|
|
- var regionRow = U.row();
|
|
|
- regionRow.addView(U.text("\u63D0\u53D6\u533A\u57DF", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
- regionRow.addView(U.flexible());
|
|
|
- regionRow.addView(W.textField(base + "regionCodes", "\u56FD\u5BB6\u7B80\u7801", 108).root);
|
|
|
- regionRow.addView(U.hgap(6));
|
|
|
- regionRow.addView(regionNote.root);
|
|
|
- rows.push(regionRow);
|
|
|
-
|
|
|
- rows.push(W.hint("\u56FD\u5BB6\u7B80\u7801\uFF1A\u5982\u65E5\u672C\u586B JP\uFF1B\u4E2D\u56FD\u53F0\u6E7E\uFF1ATW\uFF0C\u591A\u56FD\u5219\u7528 # \u9694\u5F00", false));
|
|
|
- rows.push(W.hint("\u5982\uFF1AJP#TW#CN#US\uFF0C\u4E0D\u586B\u5219\u5339\u914D\u6240\u6709\u56FD\u5BB6", false));
|
|
|
-
|
|
|
- /* —— 语言 / 昵称检测:两组「标签 + 下拉」并排。
|
|
|
- * 弹性占位放在**两组之间**,而不是每组各放一个 ——
|
|
|
- * 后者会把标签和它自己的下拉框拆到两端,读起来不知道哪个配哪个。 */
|
|
|
+ /* 语言区域 + 取样范围。
|
|
|
+ * 「取样范围」原来叫「昵称个签检测」,与「语言区域」并排却看不出两者的关系 ——
|
|
|
+ * 它其实是语言的**取样字段**(在昵称里查 / 在个签里查 / 都查),
|
|
|
+ * 而不是一个独立的过滤条件。改名之后这一行的读法就成立了。 */
|
|
|
var langRow = U.row();
|
|
|
- langRow.addView(U.text("\u8BED\u8A00\u533A\u57DF", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
+ langRow.addView(U.text("语言区域", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
langRow.addView(U.hgap(8));
|
|
|
langRow.addView(W.dropdown("langRegion", base + "langRegion").root);
|
|
|
langRow.addView(U.flexible());
|
|
|
- langRow.addView(U.text("\u6635\u79F0\u4E2A\u7B7E\u68C0\u6D4B", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
+ langRow.addView(U.text("取样范围", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
langRow.addView(U.hgap(8));
|
|
|
langRow.addView(W.dropdown("nicknameCheck", base + "nicknameCheck").root);
|
|
|
- rows.push(langRow);
|
|
|
+ blocks.push(langRow);
|
|
|
+
|
|
|
+ /* —— 以下两项:执行侧还没有承载者 ——
|
|
|
+ * 不给可操作控件。原来「提取区域」是个输入框 + 一个 chip,说明里还写着"勾选后…",
|
|
|
+ * 但那一行根本没有勾选框 —— 文案与控件自相矛盾,是最容易被当成 bug 的一处。 */
|
|
|
+ blocks.push(W.mutedRow("提取区域", Store.getText(base + "regionCodes"), function () {
|
|
|
+ Dialog.alert("提取区域 · 暂不支持",
|
|
|
+ "TikTok 的用户页**不显示账号所属国家**,没有可靠的地方能读到它,"
|
|
|
+ + "所以这一项目前不会生效。\n\n"
|
|
|
+ + "你填的国家简码(" + Store.getText(base + "regionCodes") + ")已经保留,"
|
|
|
+ + "将来接入地区数据源后会直接生效,不需要重新填。\n\n"
|
|
|
+ + "当前版本的过滤只有:私密、无头像、语言,以及下面的四组数量门槛。");
|
|
|
+ }).root);
|
|
|
+
|
|
|
+ blocks.push(W.mutedRow("AI 画像识别", "头像 · 性别 · 年龄", function () {
|
|
|
+ Dialog.alert("AI 画像识别 · 暂不支持",
|
|
|
+ "AI 头像识别 / AI 性别 / AI 年龄需要人脸与属性模型(本地模型或云端接口),"
|
|
|
+ + "当前版本没有承载者。\n\n"
|
|
|
+ + "这三项原来的设置值都已保留,将来接入后直接生效。\n\n"
|
|
|
+ + "把关掉的道理和「提取区域」一样:与其给一个开了也不发生任何事的开关,"
|
|
|
+ + "不如明确告诉你它现在不参与。");
|
|
|
+ }).root);
|
|
|
+
|
|
|
+ /* ══════════════════ 组 2 · 数值门槛 ══════════════════ */
|
|
|
+ blocks.push(featureGroup("数值门槛"));
|
|
|
+
|
|
|
+ var metrics = [
|
|
|
+ { key: "follow", label: "关注量" },
|
|
|
+ { key: "fans", label: "粉丝量" },
|
|
|
+ { key: "likes", label: "获赞量" },
|
|
|
+ { key: "videos", label: "视频量" }
|
|
|
+ ];
|
|
|
+ for (var i = 0; i < metrics.length; i++) {
|
|
|
+ var m = metrics[i];
|
|
|
+ blocks.push(W.rangeRow(base + m.key + "Min", base + m.key + "Max", m.label, "~").root);
|
|
|
+ }
|
|
|
|
|
|
- /* —— 云端去重:勾选后才出现表名与连通测试 —— */
|
|
|
- var cloudBox = W.checkRow("\u4E91\u7AEF\u53BB\u91CD", Store.getBool(base + "cloudDedupe"),
|
|
|
+ /* ══════════════════ 组 3 · 去重与输出 ══════════════════ */
|
|
|
+ blocks.push(featureGroup("去重与输出"));
|
|
|
+
|
|
|
+ /* 本地去重 / 跨轮去重。
|
|
|
+ * 这两个原来是三处控件(脚本一的「主播去重 + 去重模式」、脚本三的「UID 博主去重」),
|
|
|
+ * 分布在两张卡里,用户根本不知道它们是不是同一件事。现在收到一处、说人话。 */
|
|
|
+ var localBox = W.checkRow("本地去重", Store.getBool(base + "dedupeLocal"),
|
|
|
+ function (on) { Store.set(base + "dedupeLocal", on); });
|
|
|
+ var historyBox = W.checkRow("跨轮去重", Store.getBool(base + "dedupeHistory"),
|
|
|
+ function (on) {
|
|
|
+ Store.set(base + "dedupeHistory", on);
|
|
|
+ Pages.requestRerender(); /* 打开后会多一行说明 */
|
|
|
+ });
|
|
|
+ var dedupeRow = U.row();
|
|
|
+ U.params(localBox.root, 0, -2, 1);
|
|
|
+ U.params(historyBox.root, 0, -2, 1);
|
|
|
+ dedupeRow.addView(localBox.root);
|
|
|
+ dedupeRow.addView(historyBox.root);
|
|
|
+ blocks.push(dedupeRow);
|
|
|
+
|
|
|
+ if (Store.getBool(base + "dedupeHistory")) {
|
|
|
+ blocks.push(W.hint("跨轮 = 与历史已提取的 ID 比对,重复的跳过", false));
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 云端去重:勾选后才出现表名与连通测试 */
|
|
|
+ var cloudBox = W.checkRow("云端去重", Store.getBool(base + "cloudDedupe"),
|
|
|
function (on) {
|
|
|
Store.set(base + "cloudDedupe", on);
|
|
|
Pages.requestRerender();
|
|
|
@@ -6035,63 +6240,32 @@ function buildIdFeatures() {
|
|
|
var cloudRow = U.row();
|
|
|
cloudRow.addView(cloudBox.root);
|
|
|
cloudRow.addView(U.flexible());
|
|
|
- cloudRow.addView(W.textField(base + "cloudTable", "\u8868\u540D\uFF1A\u5B57\u7B26", 100).root);
|
|
|
+ cloudRow.addView(W.textField(base + "cloudTable", "表名:字符", 100).root);
|
|
|
cloudRow.addView(U.hgap(6));
|
|
|
- var testBtn = W.chip("\u8FDE\u901A\u6D4B\u8BD5", "accent");
|
|
|
+ var testBtn = W.chip("连通测试", "accent");
|
|
|
testBtn.root.setOnClickListener(function () {
|
|
|
var table = Store.getText(base + "cloudTable");
|
|
|
/* 如实报告:没有服务端地址时就说没配,不假装测试通过 */
|
|
|
- Dialog.alert("\u8FDE\u901A\u6D4B\u8BD5",
|
|
|
- table === "" ? "\u8BF7\u5148\u586B\u5199\u8868\u540D\u3002"
|
|
|
- : "\u8868\u540D\uFF1A" + table + "\n\n\u5F53\u524D\u672A\u914D\u7F6E\u4E91\u7AEF\u53BB\u91CD\u670D\u52A1\u5730\u5740\uFF0C\u65E0\u6CD5\u771F\u5B9E\u6D4B\u8BD5\u3002");
|
|
|
+ Dialog.alert("连通测试",
|
|
|
+ table === "" ? "请先填写表名。"
|
|
|
+ : "表名:" + table + "\n\n当前未配置云端去重服务地址,无法真实测试。");
|
|
|
});
|
|
|
cloudRow.addView(testBtn.root);
|
|
|
- rows.push(cloudRow);
|
|
|
+ blocks.push(cloudRow);
|
|
|
} else {
|
|
|
- rows.push(cloudBox.root);
|
|
|
- }
|
|
|
-
|
|
|
- /* —— AI 头像识别:勾选后 AI 性别才可改 —— */
|
|
|
- var aiRow = U.row();
|
|
|
- var aiBox = W.checkRow("AI \u5934\u50CF\u8BC6\u522B", Store.getBool(base + "aiAvatar"),
|
|
|
- function (on) {
|
|
|
- Store.set(base + "aiAvatar", on);
|
|
|
- Pages.requestRerender();
|
|
|
- });
|
|
|
- aiRow.addView(aiBox.root);
|
|
|
- aiRow.addView(U.flexible());
|
|
|
- if (Store.getBool(base + "aiAvatar")) {
|
|
|
- aiRow.addView(U.text("AI\u6027\u522B", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
- aiRow.addView(U.hgap(8));
|
|
|
- aiRow.addView(W.dropdown("aiGender", base + "aiGender").root);
|
|
|
+ blocks.push(cloudBox.root);
|
|
|
}
|
|
|
- rows.push(aiRow);
|
|
|
|
|
|
- rows.push(W.settingRow("AI \u5E74\u9F84 >",
|
|
|
- W.numberField(base + "aiAgeMax", "AI \u5E74\u9F84").root, 6));
|
|
|
-
|
|
|
- /* —— 四组数量门槛:一行一个区间,两个数各自独立可改(设计稿 16:781) —— */
|
|
|
- var metrics = [
|
|
|
- { key: "follow", label: "\u5173\u6CE8\u91CF" },
|
|
|
- { key: "fans", label: "\u7C89\u4E1D\u91CF" },
|
|
|
- { key: "likes", label: "\u83B7\u8D5E\u91CF" },
|
|
|
- { key: "videos", label: "\u89C6\u9891\u91CF" }
|
|
|
- ];
|
|
|
- for (var i = 0; i < metrics.length; i++) {
|
|
|
- var m = metrics[i];
|
|
|
- rows.push(W.rangeRow(base + m.key + "Min", base + m.key + "Max", m.label, "~").root);
|
|
|
- }
|
|
|
-
|
|
|
- /* —— 提取数量上限 —— */
|
|
|
+ /* 提取上限。0 的语义写在文案里 —— 只说"0",用户会以为是"提取 0 个" */
|
|
|
var stopRow = U.row();
|
|
|
- stopRow.addView(U.text("\u63D0\u53D6\u7B26\u5408", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
+ stopRow.addView(U.text("提取满", Theme.m.labelSize, Theme.hex.textSecondary, false));
|
|
|
stopRow.addView(U.flexible());
|
|
|
- stopRow.addView(W.numberField(base + "extractLimit", "\u63D0\u53D6\u7B26\u5408").root);
|
|
|
+ stopRow.addView(W.numberField(base + "extractLimit", "提取符合").root);
|
|
|
stopRow.addView(U.hgap(6));
|
|
|
- stopRow.addView(U.text("\u4E2A ID \u540E\u505C\u6B62", Theme.m.labelSize, Theme.hex.textMuted, false));
|
|
|
- rows.push(stopRow);
|
|
|
+ stopRow.addView(U.text("个 ID 后停止(0 = 不限)", Theme.m.labelSize, Theme.hex.textMuted, false));
|
|
|
+ blocks.push(stopRow);
|
|
|
|
|
|
- W.stack(card, rows, 12);
|
|
|
+ W.stack(card, blocks, 12);
|
|
|
return card;
|
|
|
}
|
|
|
|