diff --git a/.gitignore b/.gitignore index 6d069d6..0b4a131 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ wechat_plain/scripts/lsposed-wechat-sendmsg/*.keystore wechat_plain/scripts/platform-tools/ wechat_plain/scripts/archive/ *.idsig + +# WeChat DB 密钥:只留本地,不上传 GitLab(用 device/sync-device.sh 或 adb 从手机同步) +.wechat_key_* diff --git a/device/.dsh/skills/wechat-group-digest/SKILL.md b/device/.dsh/skills/wechat-group-digest/SKILL.md index 6505da5..b1e3845 100644 --- a/device/.dsh/skills/wechat-group-digest/SKILL.md +++ b/device/.dsh/skills/wechat-group-digest/SKILL.md @@ -106,7 +106,7 @@ WeChat Server ### Path F: send_text_via_hook — VIABLE & DEPLOYED -`send_text_via_hook(wxid: str, text: str) → dict` in `~/pixelphone/event_bus/wechat_hook_send.py`. Sends text via WeChatHook's LSPosed internal broadcast `compixelphone.wechat.SEND_MSG_INTERNAL`. No ADBKeyboard dependency, no UI disruption, sub-second round-trip, CJK-capable. +`send_text_via_hook(wxid: str, text: str) → dict` in `~/pixelphone/event_bus/wechat_hook_send.py`. Sends text via WeChatHook's LSPosed internal broadcast `com.hermes.wechat.SEND_MSG_INTERNAL`. No ADBKeyboard dependency, no UI disruption, sub-second round-trip, CJK-capable. ```python import sys @@ -433,7 +433,7 @@ result = send_text_via_hook("wxid_or_chatroom_id", "消息内容") # → {"status": "sent", "to": "...", "method": "hook_broadcast", ...} ``` -Works for both 1-on-1 and chatroom wxids. Uses `compixelphone.wechat.SEND_MSG_INTERNAL` broadcast. +Works for both 1-on-1 and chatroom wxids. Uses `com.hermes.wechat.SEND_MSG_INTERNAL` broadcast. ### Fallback: ADBKeyboard Workflow (⚠️ LEGACY — 仅作备用) diff --git a/device/README.md b/device/README.md index 03e6312..09acc88 100644 --- a/device/README.md +++ b/device/README.md @@ -11,4 +11,6 @@ | `~/.termux/boot` | `.termux/` | Termux:Boot 脚本 | | `crontab -l` | `crontab.txt` | 定时任务 | +不入库(本地同步):`wechat-viewer/.wechat_key_*`(已在 .gitignore,sync 时只拉到本地)。 + 不拉:`.db`、日志、`.env`、`.hermes_env.sh`、`.secrets.env`、`config.py`、`ocr_models`、备份。脚本末尾会扫一遍疑似密钥,命中即中止。 diff --git a/device/pixelphone/digest/wechat-group-digest/SKILL.md b/device/pixelphone/digest/wechat-group-digest/SKILL.md index 6505da5..b1e3845 100644 --- a/device/pixelphone/digest/wechat-group-digest/SKILL.md +++ b/device/pixelphone/digest/wechat-group-digest/SKILL.md @@ -106,7 +106,7 @@ WeChat Server ### Path F: send_text_via_hook — VIABLE & DEPLOYED -`send_text_via_hook(wxid: str, text: str) → dict` in `~/pixelphone/event_bus/wechat_hook_send.py`. Sends text via WeChatHook's LSPosed internal broadcast `compixelphone.wechat.SEND_MSG_INTERNAL`. No ADBKeyboard dependency, no UI disruption, sub-second round-trip, CJK-capable. +`send_text_via_hook(wxid: str, text: str) → dict` in `~/pixelphone/event_bus/wechat_hook_send.py`. Sends text via WeChatHook's LSPosed internal broadcast `com.hermes.wechat.SEND_MSG_INTERNAL`. No ADBKeyboard dependency, no UI disruption, sub-second round-trip, CJK-capable. ```python import sys @@ -433,7 +433,7 @@ result = send_text_via_hook("wxid_or_chatroom_id", "消息内容") # → {"status": "sent", "to": "...", "method": "hook_broadcast", ...} ``` -Works for both 1-on-1 and chatroom wxids. Uses `compixelphone.wechat.SEND_MSG_INTERNAL` broadcast. +Works for both 1-on-1 and chatroom wxids. Uses `com.hermes.wechat.SEND_MSG_INTERNAL` broadcast. ### Fallback: ADBKeyboard Workflow (⚠️ LEGACY — 仅作备用) diff --git a/device/pixelphone/digest/wechat-group-digest/scripts/check_digest_completeness.sh b/device/pixelphone/digest/wechat-group-digest/scripts/check_digest_completeness.sh new file mode 100755 index 0000000..d186115 --- /dev/null +++ b/device/pixelphone/digest/wechat-group-digest/scripts/check_digest_completeness.sh @@ -0,0 +1,71 @@ +#!/data/data/com.termux/files/usr/bin/sh +# 日报出图前摘要完整度闸口 +# 2026-10-03 火星人指令:出图前检查摘要完整程度,别再缺文章 +# 2026-10-04 修订(需求 reczz28JC9dkrLDn):区分 pending「未排上」与 failed「摘要失败」 +# - pending>0 ⇒ 阻塞(exit 1):摘要还没排上,先补摘要再出图 +# - failed 全为失败 ⇒ 放行(exit 0):摘要抓不出来不再拦日报(failed 文章已收录于正文「缺摘要文章」) +# - JSON 新增 failed 计数与 failed_titles;修掉末行未定义 $MISMATCH 与 su 下 curl/python3 找不到的问题 +# 用法: check_digest_completeness.sh +# 例: check_digest_completeness.sh tgo 2026-10-02 +# 退出码: 0=可出图 1=本日报窗口仍有 pending(未排上)摘要 2=前置产物缺失 +# 输出: 单行 JSON(调度器可读,人也可读) + +GROUP="${1:-tgo}" +DATE="$2" +if [ -z "$DATE" ]; then + echo '{"ok":false,"reason":"usage: check_digest_completeness.sh "}' + exit 2 +fi + +HOME_DIR=/data/data/com.termux/files/home +# su 环境下 termux bin 不在 PATH ⇒ 用全路径(2026-10-04 实测裸 curl/python3 "inaccessible or not found") +PY=/data/data/com.termux/files/usr/bin/python3 +CURL=/data/data/com.termux/files/usr/bin/curl +if [ "$GROUP" = "nmm" ]; then + MD="$HOME_DIR/daily_digest_nmm_${DATE}.md" +else + MD="$HOME_DIR/daily_digest_${GROUP}_all_${DATE}.md" +fi + +if [ ! -f "$MD" ]; then + echo "{\"ok\":false,\"reason\":\"md missing: $MD\",\"action\":\"run gen_agg_digest.py --group $GROUP --date $DATE first\"}" + exit 2 +fi + +# --- 判定1(阻塞): md 头部「内容未就绪 N 篇」= pending(未排上,需阻塞) --- +N_PENDING=0 +READY_LINE=$(grep "内容未就绪" "$MD" | tail -1) +if [ -n "$READY_LINE" ]; then + N_PENDING=$(echo "$READY_LINE" | sed 's/.*内容未就绪 \([0-9]*\) 篇.*/\1/') + case "$N_PENDING" in + ''|*[!0-9]*) N_PENDING=0 ;; # 解析失败按0,不误拦 + esac +fi + +# --- 判定1b(非阻塞): md 头部「缺摘要(失败)N 篇:标题...」= failed(摘要失败,不阻塞) --- +N_FAILED=0 +FAILED_TITLES="" +FAILED_LINE=$(grep "缺摘要(失败)" "$MD" | tail -1) +if [ -n "$FAILED_LINE" ]; then + N_FAILED=$(echo "$FAILED_LINE" | sed 's/.*缺摘要(失败)\([0-9]*\) 篇.*/\1/') + case "$N_FAILED" in + ''|*[!0-9]*) N_FAILED=0 ;; # 解析失败按0,不误拦 + esac + FAILED_TITLES=$(echo "$FAILED_LINE" | sed 's/.*://' | sed 's/"/\\"/g') +fi + +if [ "$N_PENDING" -gt 0 ]; then + echo "{\"ok\":false,\"not_ready_in_digest\":$N_PENDING,\"failed\":$N_FAILED,\"reason\":\"md 头部: 内容未就绪(pending) $N_PENDING 篇\",\"action\":\"补摘要(POST /api/articles/summarize, 间隔40-60s, 并发<=3) -> 重跑 gen_agg_digest --group $GROUP --date $DATE -> 再出图\"}" + exit 1 +fi + +# --- 判定2(非阻塞参考): viewer 全库未摘要数(可能含日报窗口外的新文章) --- +N_COMPLETE=$(grep -o "完整内容:[0-9]* 篇" "$MD" | grep -o "[0-9]*" | head -1) +[ -z "$N_COMPLETE" ] && N_COMPLETE=0 +UNSUM=$("$CURL" -s --max-time 5 "http://127.0.0.1:5001/api/articles/unsummarized?limit=200") +N_UNSUM=$(echo "$UNSUM" | "$PY" -c "import json,sys +try: print(len(json.load(sys.stdin).get('data') or [])) +except Exception: print(0)") +[ -z "$N_UNSUM" ] && N_UNSUM=0 +echo "{\"ok\":true,\"complete_in_digest\":$N_COMPLETE,\"unsummarized_total\":$N_UNSUM,\"pending\":$N_PENDING,\"failed\":$N_FAILED,\"failed_titles\":\"$FAILED_TITLES\"}" +exit 0 diff --git a/device/pixelphone/digest/wechat-group-digest/scripts/digest_gap.py b/device/pixelphone/digest/wechat-group-digest/scripts/digest_gap.py new file mode 100644 index 0000000..cb3e76a --- /dev/null +++ b/device/pixelphone/digest/wechat-group-digest/scripts/digest_gap.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""核对「日报自报消息数 vs DB 现值」(只读)。用法: digest_gap.py <组> <日期>(wechat-viewer 需在线) +微信入库可滞后数小时:01:00 生成时拿到的消息可能比最终少。差>0 ⇒ 此刻图/HTML/发送都还没发生,重跑 gen 零副作用。 +退出码: 0=差0(收工) 3=DB 比日报多(该重跑 gen) 2=无法核对(缺组配置/缺日报,不当作有差) +""" +import json +import re +import sys +import urllib.parse +import urllib.request + +BASE = "http://127.0.0.1:5001" +HOME = "/data/data/com.termux/files/home" +group, date = sys.argv[1], sys.argv[2] + +rooms = list((((json.load(open(f"{HOME}/pixelphone/group_profiles.json")).get("groups") or {}).get(group) or {}).get("chatrooms")) or {}) +if not rooms: + print(f"group={group}: group_profiles.json 里没有 chatrooms,无法核对") + sys.exit(2) + +tot = 0 +for room in rooms: # 日报「消息总数」= 各群逐群累加(非去重),与这里同口径 + u = f"{BASE}/api/messages?talker={urllib.parse.quote(room)}&date={date}" + with urllib.request.urlopen(u, timeout=25) as r: + tot += len(json.load(r).get("data") or []) + +md = f"{HOME}/daily_digest_{'tgo_all' if group == 'tgo' else group}_{date}.md" +try: + m = re.search(r"消息总数:(\d+) 条", open(md).read()) + rep = int(m.group(1)) if m else -1 +except OSError: + rep = -1 +if rep < 0: + print(f"group={group} date={date}: 读不到日报自报条数 ({md})") + sys.exit(2) + +print(f"group={group} date={date} 日报自报={rep} DB现值={tot} 差={tot - rep}") +sys.exit(3 if tot > rep else 0) diff --git a/device/pixelphone/event_bus/wechat_hook_send.py b/device/pixelphone/event_bus/wechat_hook_send.py index 9dc6651..e51c7fd 100644 --- a/device/pixelphone/event_bus/wechat_hook_send.py +++ b/device/pixelphone/event_bus/wechat_hook_send.py @@ -24,9 +24,9 @@ from typing import Any from ui_control_tools import _run, _adb_prefix, _ON_DEVICE -SEND_ACTION = "compixelphone.wechat.SEND_MSG_INTERNAL" -SEND_IMAGE_ACTION = "compixelphone.wechat.SEND_IMAGE_INTERNAL" -SEND_VOICE_CGI_ACTION = "compixelphone.wechat.SEND_VOICE_CGI" +SEND_ACTION = "com.hermes.wechat.SEND_MSG_INTERNAL" +SEND_IMAGE_ACTION = "com.hermes.wechat.SEND_IMAGE_INTERNAL" +SEND_VOICE_CGI_ACTION = "com.hermes.wechat.SEND_VOICE_CGI" _WECHAT_PKG = "com.tencent.mm" _WECHAT_MAIN = "com.tencent.mm/.ui.LauncherUI" @@ -441,7 +441,7 @@ def send_voice_via_hook(wxid: str, silk_path: str, duration_ms: int = 3000) -> d Protocol: 1. Push SILK V3 file to WeChat's cache dir (root-writable). - 2. Fire compixelphone.wechat.SEND_VOICE_INTERNAL broadcast (root-only, + 2. Fire com.hermes.wechat.SEND_VOICE_INTERNAL broadcast (root-only, RECEIVER_NOT_EXPORTED). The hook generates a random 26-char voiceId, computes voice2/{md5(id)[0:2]}/{md5(id)[2:4]}/msg_{id}.amr path, copies from cache, inserts the DB row, and calls H() for server sync. @@ -479,7 +479,7 @@ def send_voice_via_hook(wxid: str, silk_path: str, duration_ms: int = 3000) -> d # Stage 2: fire the voice broadcast cmd = ( - f"am broadcast -a compixelphone.wechat.SEND_VOICE_INTERNAL " + f"am broadcast -a com.hermes.wechat.SEND_VOICE_INTERNAL " f"--es to {wxid} " f"--es src_path {wechat_cache} " f"--ei duration {duration_ms}" diff --git a/device/pixelphone/scripts/daily-digest.sh b/device/pixelphone/scripts/daily-digest.sh index 47913cf..85b507b 100755 --- a/device/pixelphone/scripts/daily-digest.sh +++ b/device/pixelphone/scripts/daily-digest.sh @@ -1,41 +1,82 @@ #!/data/data/com.termux/files/usr/bin/bash -# TGO 8群聚合日报定时链(取代 Hermes「调度器」的日报三段)。用法: daily-digest.sh gen|img|send [YYYY-MM-DD] -# 日期默认昨天;标记 $TMPDIR/scheduler_daily__<今日> 防重,失败不写标记(下个 cron 窗口重试)。 +# 日报定时链(tgo + nmm 两组;恢复旧 Hermes 调度器的全量功能)。用法: daily-digest.sh gen|img|send [YYYY-MM-DD] +# gen : 01 点 生成聚合日报 md 并注册 report +# img : 02 点 摘要完整度闸口 → 出图 → 叠二维码 → 媒体上传 → 生成 HTML(两组串行:WanJie 图片有并发限制) +# send: 03 点 发带码 2x 图(目标读 group_profiles.json:tgo→yibooo 私聊,nmm→room 群)→ 按 hook 日志对账 +# 日期默认昨天;标记 $TMPDIR/scheduler_daily_<阶段>_<组>_<今日> 防重,失败不写标记(窗口内 cron 每 10 分钟重试)。 export PATH=/data/data/com.termux/files/usr/bin:/system/bin:$PATH HOME=${HOME:-/data/data/com.termux/files/home} export TMPDIR=${TMPDIR:-/data/data/com.termux/files/usr/tmp} [ -f "$HOME/.hermes_env.sh" ] && . "$HOME/.hermes_env.sh" # WANJIE/OSS key(不回显) -D=$HOME/pixelphone/digest -Y=${2:-$(date -d yesterday +%Y-%m-%d)} -M="$TMPDIR/scheduler_daily_${1}_$(date +%Y%m%d)" -RID=$HOME/daily_digest_tgo_all.report_id -MD=$HOME/daily_digest_tgo_all_$Y.md -PNG=$HOME/daily_digest_tgo_all_$Y.png -[ -s "$M" ] && { echo "$1 already done"; exit 0; } -case "$1" in - gen) - python3 $D/wechat-group-digest/scripts/gen_agg_digest.py --date "$Y" || exit 1 - [ -s "$RID" ] && echo "done $(date +%s)" > "$M" ;; - img) - [ -f "$MD" ] || { echo "no $MD, skip"; exit 1; } - python3 $D/gpt-image-gen/scripts/gen_daily_1tgo_image.py \ - --content-file "$MD" --report-id "$(cat $RID)" --output "$PNG" \ - --title "tgo和他的朋友们的 8 个群日报" --style watercolor || exit 1 - [ -s "$PNG" ] && echo "done $(date +%s)" > "$M" ;; - send) - [ -f "$PNG" ] || { echo "no $PNG, skip"; exit 1; } - cd $HOME/pixelphone/event_bus && python3 - "$PNG" "$M" <<'PY' -import sys, time, json -from wechat_hook_send import check_wechat_ready, send_image_via_hook -r = check_wechat_ready() -if not r.get("ready"): - print("wechat not ready:", json.dumps(r, ensure_ascii=False)); sys.exit(1) -r = send_image_via_hook("yibooo", sys.argv[1]) -print(json.dumps(r, ensure_ascii=False)) -if r.get("status") in ("ok", "sent", "success") or r.get("ok"): - open(sys.argv[2], "w").write(f"done {int(time.time())}\n") -else: - sys.exit(1) -PY - ;; - *) echo "usage: $0 gen|img|send [date]"; exit 2 ;; -esac +D=$HOME/pixelphone/digest; WG=$D/wechat-group-digest/scripts; IG=$D/gpt-image-gen/scripts +STAGE=$1; Y=${2:-$(date -d yesterday +%Y-%m-%d)}; TODAY=$(date +%Y%m%d) + +meta() { # $1=组 → P 产物前缀 / RID report_id 文件 / TITLE / STYLE / QR 叠码参数 / TARGET 发送目标(与 group_profiles.json 一致) + case "$1" in + tgo) P=$HOME/daily_digest_tgo_all; RID=$HOME/daily_digest_tgo_all.report_id; TITLE="tgo和他的朋友们的 8 个群日报" + STYLE=editorial; QR="--position top-right --size 150"; TARGET=yibooo ;; + nmm) P=$HOME/daily_digest_nmm; RID=$HOME/daily_digest_nmm.report_id; TITLE="柠萌萌AI日报" + STYLE=watercolor; QR=""; TARGET=34576206251@chatroom ;; + esac +} + +# 对账:send_digest_group.py 不看结果就写标记,所以以 LSPosed 日志里「oj() DONE to=<目标>」为准(T0 之后)。 +# ponytail: 只认目标+时间,不核对条数;上限=同一目标 T0 后重复发送也算成功。 +sent_ok() { # $1=目标 $2=T0(epoch) + # 日志会轮转(刚轮转时最新文件几乎是空的)⇒ 同时看最近两个文件 + local logs t0s; logs=$(su -c 'ls -t /data/adb/lspd/log/verbose_*.log | head -2') || return 1 + t0s=$(date -d "@$2" +%Y-%m-%dT%H:%M:%S) + # 日志刷得很快(几分钟就上千行),不能靠 tail 取尾部:直接 grep 目标再按时间过滤 + su -c "grep -h 'oj() DONE to=$1' $(echo $logs)" 2>/dev/null | awk -v t0="$t0s" \ + '{ if (match($0, /20[0-9][0-9]-[0-9-]+T[0-9:]+/) && substr($0, RSTART, 19) >= t0) f=1 } END { exit !f }' +} + +[ -n "$DIGEST_SOURCE_ONLY" ] && return 0 2>/dev/null # 仅供自检 source + +case "$STAGE" in gen|img|send) ;; *) echo "usage: $0 gen|img|send [date]"; exit 2 ;; esac +# 防并发:cron 每 10 分钟重试而出图要好几分钟;锁目录超过 40 分钟视为残留 +LOCK=$TMPDIR/daily-digest.$STAGE.lock +if ! mkdir "$LOCK" 2>/dev/null; then + [ -n "$(find "$LOCK" -maxdepth 0 -mmin +40 2>/dev/null)" ] && { rmdir "$LOCK"; mkdir "$LOCK"; } || { echo "$STAGE running, skip"; exit 0; } +fi +trap 'rmdir "$LOCK" 2>/dev/null' EXIT +# 原图保存(orig-save.sh)在用微信界面时,发送要等下一轮(发图需要一次前台切换 flush,会互相打断) +if [ "$STAGE" = send ] && [ -d "$TMPDIR/orig-save.lock" ]; then echo "orig-save 在跑,发送等下一轮"; exit 1; fi + +rc=0 +for G in tgo nmm; do + meta $G; M="$TMPDIR/scheduler_daily_${STAGE}_${G}_$TODAY" + [ -s "$M" ] && { echo "$G $STAGE already done"; continue; } + case "$STAGE" in + gen) + python3 $WG/gen_agg_digest.py --date "$Y" --group $G && [ -s "$RID" ] || { echo "$G gen failed"; rc=1; continue; } + # 漏收防线:微信入库可滞后数小时,日报自报 < DB 现值 ⇒ 此刻图/HTML/发送都还没发生,重跑 gen 零副作用 + # (重跑会再注册一条 report,删掉旧的,以最新 report_id 为准) + python3 $WG/digest_gap.py $G "$Y"; GAP=$? + if [ $GAP -eq 3 ]; then + OLD=$(cat $RID); echo "$G: DB 比日报多,重跑 gen" + if python3 $WG/gen_agg_digest.py --date "$Y" --group $G && [ "$(cat $RID)" != "$OLD" ]; then + curl -s -m 20 -X POST -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:5001/api/report/$OLD/delete >/dev/null + fi + fi + echo "done $(date +%s)" > "$M" ;; + img) + [ -f "${P}_$Y.md" ] || { echo "$G: no md, skip"; rc=1; continue; } + bash $WG/check_digest_completeness.sh $G $Y || { echo "$G: 闸口未过(缺摘要/缺md),不出图,下个窗口重试"; rc=1; continue; } + # png 比 md 新 ⇒ 上轮已出图(只是后面 HTML 等失败):不重复花生图钱;叠码也跳过(绝不在已叠码图上再叠) + if [ ! "${P}_$Y.png" -nt "${P}_$Y.md" ]; then + python3 $IG/gen_daily_1tgo_image.py --content-file "${P}_$Y.md" --report-id "$(cat $RID)" --output "${P}_$Y.png" \ + --title "$TITLE" --style $STYLE || { echo "$G: 出图失败"; rc=1; continue; } + python3 $IG/add_qr_to_image.py --image "${P}_$Y.png" --date "$Y" $QR --enlarge-2x || echo "$G: 叠码失败(警告,不阻断)" + fi + python3 $IG/collect_media_oss.py --date "$Y" --group $G --upload || echo "$G: 媒体上传失败(不阻断,墙为空)" + python3 $WG/gen_digest_html.py --date "$Y" --group $G && [ -s "${P}_$Y.png" ] && echo "done $(date +%s)" > "$M" || { echo "$G: HTML 失败"; rc=1; } ;; + send) + [ -s "${P}_$Y.png" ] || { echo "$G: no png, skip"; rc=1; continue; } + T0=$(date +%s) + python3 $WG/send_digest_group.py --group $G --date "$Y" || { rm -f "$M"; rc=1; continue; } + sleep 20 + if sent_ok "$TARGET" "$T0"; then echo "$G sent+verified -> $TARGET"; echo "done $(date +%s)" > "$M" + else echo "$G: hook 日志里没有 oj() DONE,视为未送达,下个窗口重试"; rm -f "$M"; rc=1; fi ;; + esac +done +exit $rc diff --git a/device/pixelphone/scripts/orig-save.sh b/device/pixelphone/scripts/orig-save.sh new file mode 100755 index 0000000..8cc17a1 --- /dev/null +++ b/device/pixelphone/scripts/orig-save.sh @@ -0,0 +1,35 @@ +#!/data/data/com.termux/files/usr/bin/bash +# 原图保存 A+B(旧 Hermes 调度器移植;窗口 02/06/10/14/18/22 点,用户 2026-08-24 定频,crontab 放在每窗口 :15 避开 02:00 出图) +# A: 微信图片查看器长按「保存图片」把当日群图的明文原图存到相册(UI 自动化,约 30 分钟,接管屏幕;--date 取【当日】, +# 供次日 01:00 生成当日日报用) +# B: 明文匹配 + 上传 OSS,更新 media_digest/media_files(幂等;--date 取【昨天】,供 HTML 媒体墙) +# 前置:锁屏则整轮跳过;微信没起跳过;有残留任务跳过。测试用环境变量: ORIG_SAVE_ARGS="--group --max-swipes 2" +export PATH=/data/data/com.termux/files/usr/bin:/system/bin:$PATH HOME=${HOME:-/data/data/com.termux/files/home} +export TMPDIR=${TMPDIR:-/data/data/com.termux/files/usr/tmp} +[ -f "$HOME/.hermes_env.sh" ] && . "$HOME/.hermes_env.sh" # OSS key(不回显) +IG=$HOME/pixelphone/digest/gpt-image-gen/scripts +TODAY=$(date +%Y-%m-%d); YDAY=$(date -d yesterday +%Y-%m-%d) +LOG=$HOME/tmp/orig_save_$(date +%Y%m%d_%H%M).log +mkdir -p "$HOME/tmp" +say() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG"; } + +LOCK=$TMPDIR/orig-save.lock # send 阶段看到它在就等下一轮(两边都要用微信界面) +if ! mkdir "$LOCK" 2>/dev/null; then + [ -n "$(find "$LOCK" -maxdepth 0 -mmin +90 2>/dev/null)" ] && { rmdir "$LOCK"; mkdir "$LOCK"; } || { say "上一轮还在跑,跳过"; exit 0; } +fi +trap 'rmdir "$LOCK" 2>/dev/null' EXIT + +[ "$(su -c 'dumpsys window 2>/dev/null | grep -c mDreamingLockscreen=true')" = "1" ] && { say "锁屏,跳过原图保存"; exit 0; } +[ -n "$(su -c 'pidof com.tencent.mm' 2>/dev/null)" ] || { say "微信没在运行,跳过"; exit 0; } +BUSY=$(ps -ef | grep -E 'rescue_originals|send_digest|gen_daily|gen_agg|collect_media|save_originals|upload_originals' | grep -v grep | grep -v "orig-save" | wc -l) +[ "$BUSY" -gt 0 ] && { say "有 $BUSY 个日报/原图任务在跑,跳过"; exit 0; } + +say "== A 保存当日原图 date=$TODAY ${ORIG_SAVE_ARGS:-}" +python3 "$IG/save_originals_batch.py" --date "$TODAY" --max-swipes 18 ${ORIG_SAVE_ARGS:-} >> "$LOG" 2>&1; A=$? +say "A_EXIT=$A" +say "== B 匹配并上传昨日原图 date=$YDAY" +python3 "$IG/upload_originals.py" --date "$YDAY" --upload >> "$LOG" 2>&1; B=$? +say "B_EXIT=$B" +for i in 1 2 3; do su -c "input keyevent 4" 2>/dev/null; sleep 1; done # 复位副作用:退回微信主界面,避免残页影响下一轮 +say "== END (A=$A B=$B) log=$LOG" +[ $A -eq 0 ] && [ $B -eq 0 ] diff --git a/device/sync-device.sh b/device/sync-device.sh index fd3907b..f3feafb 100755 --- a/device/sync-device.sh +++ b/device/sync-device.sh @@ -26,13 +26,19 @@ chmod -R a+r $REMOTE; wc -l $REMOTE/list.txt rm -rf _extract && mkdir _extract "$ADB" pull $REMOTE/device.tgz _extract/ >/dev/null "$ADB" pull $REMOTE/crontab.txt crontab.txt >/dev/null -"$ADB" shell "su -c 'rm -rf $REMOTE'" # 重新生成:先清掉旧快照再解开,避免已删文件残留 find . -mindepth 1 -maxdepth 1 ! -name sync-device.sh ! -name _extract ! -name crontab.txt ! -name README.md -exec rm -rf {} + tar xzf _extract/device.tgz -C . 2>/dev/null || true # service.d 脚本带绝对路径解开在 data/adb/...,挪到 service.d/ if [ -d data/adb/service.d ]; then mv data/adb/service.d service.d; rm -rf data; fi rm -rf _extract +# 微信库密钥:只同步到本地,不入库(.gitignore 的 .wechat_key_*) +"$ADB" shell "su -c 'mkdir -p $REMOTE; cp $H/wechat-viewer/.wechat_key_* $REMOTE/ && chmod -R a+r $REMOTE'" 2>/dev/null || true +mkdir -p wechat-viewer +for k in $("$ADB" shell "su -c 'ls -a $REMOTE'" 2>/dev/null | tr -d '\r' | grep '^\.wechat_key_'); do + "$ADB" pull "$REMOTE/$k" "wechat-viewer/$k" >/dev/null && chmod 600 "wechat-viewer/$k" +done +"$ADB" shell "su -c 'rm -rf $REMOTE'" # 兜底:扫一遍疑似密钥,命中就报警并中止(不留在工作区) if grep -rIlE '(sk-[A-Za-z0-9]{20,}|LTAI[A-Za-z0-9]{10,}|AccessKeySecret|api[_-]?key["'"'"' ]*[:=]["'"'"' ]*[A-Za-z0-9]{24,})' . --exclude=sync-device.sh --exclude=README.md 2>/dev/null; then echo "!! 上面文件疑似含密钥,请检查后再提交" >&2; exit 1 diff --git a/wechat-viewer/.wechat_key_ec4f423b316d41e9af5e8398155cf105 b/wechat-viewer/.wechat_key_ec4f423b316d41e9af5e8398155cf105 deleted file mode 100644 index dd39bfc..0000000 --- a/wechat-viewer/.wechat_key_ec4f423b316d41e9af5e8398155cf105 +++ /dev/null @@ -1 +0,0 @@ -1986554