mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-10 15:08:31 +00:00
Compare commits
21 Commits
dependabot
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56b767ff46 | ||
|
|
102eb14b0b | ||
|
|
e57b9d07f1 | ||
|
|
3be90d00e5 | ||
|
|
efb5cd3586 | ||
|
|
86b1043134 | ||
|
|
36bed846b2 | ||
|
|
9d3d38fa7e | ||
|
|
ddf6b63aec | ||
|
|
079779c2c6 | ||
|
|
afa8bb5fe0 | ||
|
|
127668ae22 | ||
|
|
b00264d060 | ||
|
|
2e135587d4 | ||
|
|
571bffa923 | ||
|
|
bc355d43a0 | ||
|
|
e2a207be92 | ||
|
|
397cc888db | ||
|
|
22a2616534 | ||
|
|
657e8015b2 | ||
|
|
d96000f0d9 |
66
.github/workflows/dev-daily-fixed.yml
vendored
66
.github/workflows/dev-daily-fixed.yml
vendored
@@ -60,7 +60,23 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
gh release create "$FIXED_DEV_TAG" --repo "$GITHUB_REPOSITORY" --title "Daily Dev Build" --notes "开发版发布页" --prerelease --target "$TARGET_BRANCH"
|
gh release create "$FIXED_DEV_TAG" --repo "$GITHUB_REPOSITORY" --title "Daily Dev Build" --notes "开发版发布页" --prerelease --target "$TARGET_BRANCH"
|
||||||
RELEASE_REST_ID="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$FIXED_DEV_TAG" --jq '.id')"
|
RELEASE_REST_ID="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$FIXED_DEV_TAG" --jq '.id')"
|
||||||
gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/$RELEASE_REST_ID" -f draft=false -f prerelease=true >/dev/null
|
RELEASE_ENDPOINT="repos/$GITHUB_REPOSITORY/releases/tags/$FIXED_DEV_TAG"
|
||||||
|
settled="false"
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/$RELEASE_REST_ID" -F draft=false -F prerelease=true >/dev/null 2>&1 || true
|
||||||
|
DRAFT_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.draft' 2>/dev/null || echo true)"
|
||||||
|
PRERELEASE_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.prerelease' 2>/dev/null || echo false)"
|
||||||
|
if [ "$DRAFT_STATE" = "false" ] && [ "$PRERELEASE_STATE" = "true" ]; then
|
||||||
|
settled="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "$settled" != "true" ]; then
|
||||||
|
echo "Failed to settle release state after create:"
|
||||||
|
gh api "$RELEASE_ENDPOINT" --jq '{draft: .draft, prerelease: .prerelease, url: .html_url}'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
dev-mac-arm64:
|
dev-mac-arm64:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
@@ -81,6 +97,22 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Ensure mac key helpers are executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for file in \
|
||||||
|
resources/key/macos/universal/xkey_helper \
|
||||||
|
resources/key/macos/universal/image_scan_helper \
|
||||||
|
resources/key/macos/universal/xkey_helper_macos \
|
||||||
|
resources/key/macos/universal/libwx_key.dylib
|
||||||
|
do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
chmod +x "$file"
|
||||||
|
ls -l "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Set dev version
|
- name: Set dev version
|
||||||
shell: bash
|
shell: bash
|
||||||
run: npm version "${{ needs.prepare.outputs.dev_version }}" --no-git-tag-version --allow-same-version
|
run: npm version "${{ needs.prepare.outputs.dev_version }}" --no-git-tag-version --allow-same-version
|
||||||
@@ -270,21 +302,25 @@ jobs:
|
|||||||
- name: Update fixed dev release notes
|
- name: Update fixed dev release notes
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
FIXED_DEV_TAG: ${{ env.FIXED_DEV_TAG }}
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TAG="$FIXED_DEV_TAG"
|
TAG="${FIXED_DEV_TAG:-}"
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
echo "FIXED_DEV_TAG is empty, abort."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
REPO="$GITHUB_REPOSITORY"
|
REPO="$GITHUB_REPOSITORY"
|
||||||
RELEASE_PAGE="https://github.com/$REPO/releases/tag/$TAG"
|
RELEASE_PAGE="https://github.com/$REPO/releases/tag/$TAG"
|
||||||
|
echo "Using release tag: $TAG"
|
||||||
|
|
||||||
if ! gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then
|
if ! gh api "repos/$REPO/releases/tags/$TAG" >/dev/null 2>&1; then
|
||||||
echo "Release $TAG not found, skip notes update."
|
echo "Release $TAG not found, skip notes update."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ASSETS_JSON="$(gh release view "$TAG" --repo "$REPO" --json assets)"
|
ASSETS_JSON="$(gh api "repos/$REPO/releases/tags/$TAG")"
|
||||||
|
|
||||||
pick_asset() {
|
pick_asset() {
|
||||||
local pattern="$1"
|
local pattern="$1"
|
||||||
@@ -350,4 +386,22 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_release_notes
|
update_release_notes
|
||||||
gh release view "$TAG" --repo "$REPO" --json isDraft,isPrerelease,url
|
RELEASE_REST_ID="$(gh api "repos/$REPO/releases/tags/$TAG" --jq '.id')"
|
||||||
|
RELEASE_ENDPOINT="repos/$REPO/releases/tags/$TAG"
|
||||||
|
settled="false"
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
gh api --method PATCH "repos/$REPO/releases/$RELEASE_REST_ID" -F draft=false -F prerelease=true >/dev/null 2>&1 || true
|
||||||
|
DRAFT_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.draft' 2>/dev/null || echo true)"
|
||||||
|
PRERELEASE_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.prerelease' 2>/dev/null || echo false)"
|
||||||
|
if [ "$DRAFT_STATE" = "false" ] && [ "$PRERELEASE_STATE" = "true" ]; then
|
||||||
|
settled="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "$settled" != "true" ]; then
|
||||||
|
echo "Failed to settle release state after notes update:"
|
||||||
|
gh api "$RELEASE_ENDPOINT" --jq '{draft: .draft, prerelease: .prerelease, url: .html_url}'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gh api "repos/$REPO/releases/tags/$TAG" --jq '{isDraft: .draft, isPrerelease: .prerelease, url: .html_url}'
|
||||||
|
|||||||
65
.github/workflows/preview-nightly-main.yml
vendored
65
.github/workflows/preview-nightly-main.yml
vendored
@@ -86,7 +86,23 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
gh release create "$FIXED_PREVIEW_TAG" --repo "$GITHUB_REPOSITORY" --title "Preview Nightly Build" --notes "预览版发布页" --prerelease --target "$TARGET_BRANCH"
|
gh release create "$FIXED_PREVIEW_TAG" --repo "$GITHUB_REPOSITORY" --title "Preview Nightly Build" --notes "预览版发布页" --prerelease --target "$TARGET_BRANCH"
|
||||||
RELEASE_REST_ID="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$FIXED_PREVIEW_TAG" --jq '.id')"
|
RELEASE_REST_ID="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$FIXED_PREVIEW_TAG" --jq '.id')"
|
||||||
gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/$RELEASE_REST_ID" -f draft=false -f prerelease=true >/dev/null
|
RELEASE_ENDPOINT="repos/$GITHUB_REPOSITORY/releases/tags/$FIXED_PREVIEW_TAG"
|
||||||
|
settled="false"
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/$RELEASE_REST_ID" -F draft=false -F prerelease=true >/dev/null 2>&1 || true
|
||||||
|
DRAFT_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.draft' 2>/dev/null || echo true)"
|
||||||
|
PRERELEASE_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.prerelease' 2>/dev/null || echo false)"
|
||||||
|
if [ "$DRAFT_STATE" = "false" ] && [ "$PRERELEASE_STATE" = "true" ]; then
|
||||||
|
settled="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "$settled" != "true" ]; then
|
||||||
|
echo "Failed to settle release state after create:"
|
||||||
|
gh api "$RELEASE_ENDPOINT" --jq '{draft: .draft, prerelease: .prerelease, url: .html_url}'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
preview-mac-arm64:
|
preview-mac-arm64:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
@@ -108,6 +124,22 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Ensure mac key helpers are executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for file in \
|
||||||
|
resources/key/macos/universal/xkey_helper \
|
||||||
|
resources/key/macos/universal/image_scan_helper \
|
||||||
|
resources/key/macos/universal/xkey_helper_macos \
|
||||||
|
resources/key/macos/universal/libwx_key.dylib
|
||||||
|
do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
chmod +x "$file"
|
||||||
|
ls -l "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Set preview version
|
- name: Set preview version
|
||||||
shell: bash
|
shell: bash
|
||||||
run: npm version "${{ needs.prepare.outputs.preview_version }}" --no-git-tag-version --allow-same-version
|
run: npm version "${{ needs.prepare.outputs.preview_version }}" --no-git-tag-version --allow-same-version
|
||||||
@@ -315,17 +347,22 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TAG="$FIXED_PREVIEW_TAG"
|
TAG="${FIXED_PREVIEW_TAG:-}"
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
echo "FIXED_PREVIEW_TAG is empty, abort."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
CURRENT_PREVIEW_VERSION="${{ needs.prepare.outputs.preview_version }}"
|
CURRENT_PREVIEW_VERSION="${{ needs.prepare.outputs.preview_version }}"
|
||||||
REPO="$GITHUB_REPOSITORY"
|
REPO="$GITHUB_REPOSITORY"
|
||||||
RELEASE_PAGE="https://github.com/$REPO/releases/tag/$TAG"
|
RELEASE_PAGE="https://github.com/$REPO/releases/tag/$TAG"
|
||||||
|
echo "Using release tag: $TAG"
|
||||||
|
|
||||||
if ! gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then
|
if ! gh api "repos/$REPO/releases/tags/$TAG" >/dev/null 2>&1; then
|
||||||
echo "Release $TAG not found (possibly all publish jobs failed), skip notes update."
|
echo "Release $TAG not found (possibly all publish jobs failed), skip notes update."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ASSETS_JSON="$(gh release view "$TAG" --repo "$REPO" --json assets)"
|
ASSETS_JSON="$(gh api "repos/$REPO/releases/tags/$TAG")"
|
||||||
|
|
||||||
pick_asset() {
|
pick_asset() {
|
||||||
local pattern="$1"
|
local pattern="$1"
|
||||||
@@ -392,4 +429,22 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_release_notes
|
update_release_notes
|
||||||
gh release view "$TAG" --repo "$REPO" --json isDraft,isPrerelease,url
|
RELEASE_REST_ID="$(gh api "repos/$REPO/releases/tags/$TAG" --jq '.id')"
|
||||||
|
RELEASE_ENDPOINT="repos/$REPO/releases/tags/$TAG"
|
||||||
|
settled="false"
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
gh api --method PATCH "repos/$REPO/releases/$RELEASE_REST_ID" -F draft=false -F prerelease=true >/dev/null 2>&1 || true
|
||||||
|
DRAFT_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.draft' 2>/dev/null || echo true)"
|
||||||
|
PRERELEASE_STATE="$(gh api "$RELEASE_ENDPOINT" --jq '.prerelease' 2>/dev/null || echo false)"
|
||||||
|
if [ "$DRAFT_STATE" = "false" ] && [ "$PRERELEASE_STATE" = "true" ]; then
|
||||||
|
settled="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "$settled" != "true" ]; then
|
||||||
|
echo "Failed to settle release state after notes update:"
|
||||||
|
gh api "$RELEASE_ENDPOINT" --jq '{draft: .draft, prerelease: .prerelease, url: .html_url}'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gh api "repos/$REPO/releases/tags/$TAG" --jq '{isDraft: .draft, isPrerelease: .prerelease, url: .html_url}'
|
||||||
|
|||||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -31,6 +31,22 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Ensure mac key helpers are executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for file in \
|
||||||
|
resources/key/macos/universal/xkey_helper \
|
||||||
|
resources/key/macos/universal/image_scan_helper \
|
||||||
|
resources/key/macos/universal/xkey_helper_macos \
|
||||||
|
resources/key/macos/universal/libwx_key.dylib
|
||||||
|
do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
chmod +x "$file"
|
||||||
|
ls -l "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Sync version with tag
|
- name: Sync version with tag
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
67
README.md
67
README.md
@@ -1,34 +1,23 @@
|
|||||||
# WeFlow
|
# WeFlow
|
||||||
|
|
||||||
WeFlow 是一个**完全本地**的微信**实时**聊天记录查看、分析与导出工具。它可以实时获取你的微信聊天记录并将其导出,还可以根据你的聊天记录为你生成独一无二的分析报告
|
WeFlow 是一个**完全本地**的微信**实时**聊天记录查看、分析与导出工具。它可以实时获取你的微信聊天记录并将其导出,还可以根据你的聊天记录为你生成独一无二的分析报告。
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="app.png" alt="WeFlow" width="90%">
|
<img src="app.png" alt="WeFlow 应用预览" width="90%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/hicccc77/WeFlow/stargazers">
|
<!-- 第一行修复样式 -->
|
||||||
<img src="https://img.shields.io/github/stars/hicccc77/WeFlow?style=flat-square" alt="Stargazers">
|
<a href="https://github.com/hicccc77/WeFlow/stargazers"><img src="https://img.shields.io/github/stars/hicccc77/WeFlow?style=flat&label=Stars&labelColor=1F2937&color=2563EB" alt="Stargazers"></a>
|
||||||
</a>
|
<a href="https://github.com/hicccc77/WeFlow/network/members"><img src="https://img.shields.io/github/forks/hicccc77/WeFlow?style=flat&label=Forks&labelColor=1F2937&color=7C3AED" alt="Forks"></a>
|
||||||
<a href="https://github.com/hicccc77/WeFlow/network/members">
|
<a href="https://github.com/hicccc77/WeFlow/issues"><img src="https://img.shields.io/github/issues/hicccc77/WeFlow?style=flat&label=Issues&labelColor=1F2937&color=D97706" alt="Issues"></a>
|
||||||
<img src="https://img.shields.io/github/forks/hicccc77/WeFlow?style=flat-square" alt="Forks">
|
<a href="https://github.com/hicccc77/WeFlow/releases"><img src="https://img.shields.io/github/downloads/hicccc77/WeFlow/total?style=flat&label=Downloads&labelColor=1F2937&color=059669" alt="Downloads"></a>
|
||||||
</a>
|
<br><br>
|
||||||
<a href="https://github.com/hicccc77/WeFlow/issues">
|
<!-- 第二行:电报矮一点(22px),排名高一点(32px),使用 vertical-align: middle 居中对齐 -->
|
||||||
<img src="https://img.shields.io/github/issues/hicccc77/WeFlow?style=flat-square" alt="Issues">
|
<a href="https://t.me/weflow_cc"><img src="https://img.shields.io/badge/Telegram-频道-1D9BF0?style=flat&logo=telegram&logoColor=white&labelColor=1F2937&color=1D9BF0" alt="Telegram Channel" style="height: 22px; vertical-align: middle;"></a>
|
||||||
</a>
|
<a href="https://www.star-history.com/hicccc77/weflow"><img src="https://api.star-history.com/badge?repo=hicccc77/WeFlow&theme=dark" alt="Star History Rank" style="height: 32px; vertical-align: middle;"></a>
|
||||||
<a href="https://github.com/hicccc77/WeFlow/releases">
|
|
||||||
<img src="https://img.shields.io/github/downloads/hicccc77/WeFlow/total?style=flat-square" alt="Downloads" />
|
|
||||||
</a>
|
|
||||||
<a href="https://t.me/weflow_cc">
|
|
||||||
<img src="https://img.shields.io/badge/Telegram%20频道-0088cc?style=flat-square&logo=telegram&logoColor=0088cc&labelColor=white" alt="Telegram">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> 如果导出聊天记录后,想深入分析聊天内容可以试试 [ChatLab](https://chatlab.fun/)
|
> 如果导出聊天记录后,想深入分析聊天内容可以试试 [ChatLab](https://chatlab.fun/)
|
||||||
|
|
||||||
@@ -47,14 +36,12 @@ WeFlow 是一个**完全本地**的微信**实时**聊天记录查看、分析
|
|||||||
|
|
||||||
## 支持平台与设备
|
## 支持平台与设备
|
||||||
|
|
||||||
|
|
||||||
| 平台 | 设备/架构 | 安装包 |
|
| 平台 | 设备/架构 | 安装包 |
|
||||||
|------|----------|--------|
|
|------|----------|--------|
|
||||||
| Windows | Windows10+、x64(amd64) | `.exe` |
|
| Windows | Windows10+、x64(amd64) | `.exe` |
|
||||||
| macOS | Apple Silicon(M 系列,arm64) | `.dmg` |
|
| macOS | Apple Silicon(M 系列,arm64) | `.dmg` |
|
||||||
| Linux | x64 设备(amd64) | `.AppImage`、`.tar.gz` |
|
| Linux | x64 设备(amd64) | `.AppImage`、`.tar.gz` |
|
||||||
|
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
若你只想使用成品版本,可前往 [Releases](https://github.com/hicccc77/WeFlow/releases) 下载并安装。
|
若你只想使用成品版本,可前往 [Releases](https://github.com/hicccc77/WeFlow/releases) 下载并安装。
|
||||||
@@ -93,7 +80,6 @@ WeFlow 提供本地 HTTP API 服务,支持通过接口查询消息数据,可
|
|||||||
|
|
||||||
完整接口文档:[点击查看](docs/HTTP-API.md)
|
完整接口文档:[点击查看](docs/HTTP-API.md)
|
||||||
|
|
||||||
|
|
||||||
## 面向开发者
|
## 面向开发者
|
||||||
|
|
||||||
如果你想从源码构建或为项目贡献代码,请遵循以下步骤:
|
如果你想从源码构建或为项目贡献代码,请遵循以下步骤:
|
||||||
@@ -108,9 +94,24 @@ npm install
|
|||||||
|
|
||||||
# 3. 运行应用(开发模式)
|
# 3. 运行应用(开发模式)
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 构建状态
|
||||||
|
|
||||||
|
用于开发者排查发布链路,普通用户可忽略:
|
||||||
|
|
||||||
|
<p align="left">
|
||||||
|
<a href="https://github.com/hicccc77/WeFlow/actions/workflows/release.yml">
|
||||||
|
<img src="https://img.shields.io/github/actions/workflow/status/hicccc77/WeFlow/release.yml?branch=main&label=Release&style=flat&labelColor=111827&color=22C55E" alt="Release Workflow">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/hicccc77/WeFlow/actions/workflows/preview-nightly-main.yml">
|
||||||
|
<img src="https://img.shields.io/github/actions/workflow/status/hicccc77/WeFlow/preview-nightly-main.yml?branch=main&label=Preview%20Nightly&style=flat&labelColor=111827&color=F59E0B" alt="Preview Nightly Workflow">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/hicccc77/WeFlow/actions/workflows/dev-daily-fixed.yml">
|
||||||
|
<img src="https://img.shields.io/github/actions/workflow/status/hicccc77/WeFlow/dev-daily-fixed.yml?branch=dev&label=Dev%20Daily&style=flat&labelColor=111827&color=A78BFA" alt="Dev Daily Workflow">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## 致谢
|
## 致谢
|
||||||
|
|
||||||
- [密语 CipherTalk](https://github.com/ILoveBingLu/miyu) 为本项目提供了基础框架
|
- [密语 CipherTalk](https://github.com/ILoveBingLu/miyu) 为本项目提供了基础框架
|
||||||
@@ -120,18 +121,16 @@ npm run dev
|
|||||||
|
|
||||||
如果 WeFlow 确实帮到了你,可以考虑请我们喝杯咖啡:
|
如果 WeFlow 确实帮到了你,可以考虑请我们喝杯咖啡:
|
||||||
|
|
||||||
|
> TRC20 **Address:** `TZCtAw8CaeARWZBfvjidCnTcfnAtf6nvS6`
|
||||||
> TRC20 **Address:** `TZCtAw8CaeARWZBfvjidCnTcfnAtf6nvS6`
|
|
||||||
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
<a href="https://www.star-history.com/#hicccc77/WeFlow&type=date&legend=top-left">
|
<a href="https://www.star-history.com/#hicccc77/WeFlow&type=date&legend=top-left">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&theme=dark&legend=top-left" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&theme=dark&legend=top-left" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&legend=top-left" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&legend=top-left" />
|
||||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&legend=top-left" />
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&legend=top-left" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|||||||
@@ -270,7 +270,9 @@ export class ConfigService {
|
|||||||
const inLockMode = this.isLockMode() && this.unlockPassword
|
const inLockMode = this.isLockMode() && this.unlockPassword
|
||||||
|
|
||||||
if (ENCRYPTED_BOOL_KEYS.has(key)) {
|
if (ENCRYPTED_BOOL_KEYS.has(key)) {
|
||||||
toStore = this.safeEncrypt(String(value)) as ConfigSchema[K]
|
const boolValue = value === true || value === 'true'
|
||||||
|
// `false` 不需要写入 keychain,避免无意义触发 macOS 钥匙串弹窗
|
||||||
|
toStore = (boolValue ? this.safeEncrypt('true') : false) as ConfigSchema[K]
|
||||||
} else if (ENCRYPTED_NUMBER_KEYS.has(key)) {
|
} else if (ENCRYPTED_NUMBER_KEYS.has(key)) {
|
||||||
if (inLockMode && LOCKABLE_NUMBER_KEYS.has(key)) {
|
if (inLockMode && LOCKABLE_NUMBER_KEYS.has(key)) {
|
||||||
toStore = this.lockEncrypt(String(value), this.unlockPassword!) as ConfigSchema[K]
|
toStore = this.lockEncrypt(String(value), this.unlockPassword!) as ConfigSchema[K]
|
||||||
@@ -649,7 +651,7 @@ export class ConfigService {
|
|||||||
|
|
||||||
clearHelloSecret(): void {
|
clearHelloSecret(): void {
|
||||||
this.store.set('authHelloSecret', '' as any)
|
this.store.set('authHelloSecret', '' as any)
|
||||||
this.store.set('authUseHello', this.safeEncrypt('false') as any)
|
this.store.set('authUseHello', false as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 迁移 ===
|
// === 迁移 ===
|
||||||
@@ -658,13 +660,18 @@ export class ConfigService {
|
|||||||
// 将旧版明文 auth 字段迁移为 safeStorage 加密格式
|
// 将旧版明文 auth 字段迁移为 safeStorage 加密格式
|
||||||
// 如果已经是 safe: 或 lock: 前缀则跳过
|
// 如果已经是 safe: 或 lock: 前缀则跳过
|
||||||
const rawEnabled: any = this.store.get('authEnabled')
|
const rawEnabled: any = this.store.get('authEnabled')
|
||||||
if (typeof rawEnabled === 'boolean') {
|
if (rawEnabled === true || rawEnabled === 'true') {
|
||||||
this.store.set('authEnabled', this.safeEncrypt(String(rawEnabled)) as any)
|
this.store.set('authEnabled', this.safeEncrypt('true') as any)
|
||||||
|
} else if (rawEnabled === false || rawEnabled === 'false') {
|
||||||
|
// 保持 false 为明文布尔,避免冷启动访问 keychain
|
||||||
|
this.store.set('authEnabled', false as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawUseHello: any = this.store.get('authUseHello')
|
const rawUseHello: any = this.store.get('authUseHello')
|
||||||
if (typeof rawUseHello === 'boolean') {
|
if (rawUseHello === true || rawUseHello === 'true') {
|
||||||
this.store.set('authUseHello', this.safeEncrypt(String(rawUseHello)) as any)
|
this.store.set('authUseHello', this.safeEncrypt('true') as any)
|
||||||
|
} else if (rawUseHello === false || rawUseHello === 'false') {
|
||||||
|
this.store.set('authUseHello', false as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawPassword: any = this.store.get('authPassword')
|
const rawPassword: any = this.store.get('authPassword')
|
||||||
@@ -729,7 +736,7 @@ export class ConfigService {
|
|||||||
// === 工具方法 ===
|
// === 工具方法 ===
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前 wxid 对应的图片密钥,优先从 wxidConfigs 中取,找不到则回退到全局<EFBFBD><EFBFBD>置
|
* 获取当前 wxid 对应的图片密钥,优先从 wxidConfigs 中取,找不到则回退到全局配置
|
||||||
*/
|
*/
|
||||||
getImageKeysForCurrentWxid(): { xorKey: unknown; aesKey: string } {
|
getImageKeysForCurrentWxid(): { xorKey: unknown; aesKey: string } {
|
||||||
const wxid = this.get('myWxid')
|
const wxid = this.get('myWxid')
|
||||||
|
|||||||
@@ -949,7 +949,7 @@ export class ImageDecryptService {
|
|||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 绛栫暐 B: 鏂扮増 Session 鍝堝笇璺緞鐚滄祴 ---
|
// --- 策略 B: 新版 Session 哈希路径猜测 ---
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(root, { withFileTypes: true })
|
const entries = await fs.readdir(root, { withFileTypes: true })
|
||||||
const sessionDirs = entries
|
const sessionDirs = entries
|
||||||
@@ -1854,7 +1854,7 @@ export class ImageDecryptService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浠?wxgf 鏁版嵁涓彁鍙?HEVC NALU 瑁告祦
|
* 从 wxgf 数据中提取 HEVC NALU 裸流
|
||||||
*/
|
*/
|
||||||
private extractHevcNalu(buffer: Buffer): Buffer | null {
|
private extractHevcNalu(buffer: Buffer): Buffer | null {
|
||||||
const nalUnits: Buffer[] = []
|
const nalUnits: Buffer[] = []
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ class InsightService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测<EFBFBD><EFBFBD><EFBFBD> API 连接,返回 { success, message }。
|
* 测试 API 连接,返回 { success, message }。
|
||||||
* 供设置页"测试连接"按钮调用。
|
* 供设置页"测试连接"按钮调用。
|
||||||
*/
|
*/
|
||||||
async testConnection(): Promise<{ success: boolean; message: string }> {
|
async testConnection(): Promise<{ success: boolean; message: string }> {
|
||||||
@@ -475,7 +475,7 @@ class InsightService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取今日全局已触发次数(所有会话合计),用于 prompt 中告知模<EFBFBD><EFBFBD><EFBFBD>全局上下文。
|
* 获取今日全局已触发次数(所有会话合计),用于 prompt 中告知模型全局上下文。
|
||||||
*/
|
*/
|
||||||
private getTodayTotalTriggerCount(): number {
|
private getTodayTotalTriggerCount(): number {
|
||||||
this.resetIfNewDay()
|
this.resetIfNewDay()
|
||||||
@@ -709,7 +709,7 @@ class InsightService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 构建 prompt ─────────────<EFBFBD><EFBFBD><EFBFBD>───────────────────────────────<EFBFBD><EFBFBD><EFBFBD>────────────
|
// ── 构建 prompt ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// 今日触发统计(让模型具备时间与克制感)
|
// 今日触发统计(让模型具备时间与克制感)
|
||||||
const sessionTriggerTimes = this.recordTrigger(sessionId)
|
const sessionTriggerTimes = this.recordTrigger(sessionId)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { app, shell } from 'electron'
|
import { app, shell } from 'electron'
|
||||||
import { join, basename, dirname } from 'path'
|
import { join, basename, dirname } from 'path'
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
|
import { existsSync, readdirSync, readFileSync, statSync, chmodSync } from 'fs'
|
||||||
import { execFile, spawn } from 'child_process'
|
import { execFile, spawn } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
@@ -403,19 +403,71 @@ export class KeyServiceMac {
|
|||||||
return `'${String(text).replace(/'/g, `'\\''`)}'`
|
return `'${String(text).replace(/'/g, `'\\''`)}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private collectMacKeyArtifactPaths(primaryBinaryPath: string): string[] {
|
||||||
|
const baseDir = dirname(primaryBinaryPath)
|
||||||
|
const names = ['xkey_helper', 'image_scan_helper', 'xkey_helper_macos', 'libwx_key.dylib']
|
||||||
|
const unique: string[] = []
|
||||||
|
for (const name of names) {
|
||||||
|
const full = join(baseDir, name)
|
||||||
|
if (!existsSync(full)) continue
|
||||||
|
if (!unique.includes(full)) unique.push(full)
|
||||||
|
}
|
||||||
|
if (existsSync(primaryBinaryPath) && !unique.includes(primaryBinaryPath)) {
|
||||||
|
unique.unshift(primaryBinaryPath)
|
||||||
|
}
|
||||||
|
return unique
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureExecutableBitsBestEffort(paths: string[]): void {
|
||||||
|
for (const p of paths) {
|
||||||
|
try {
|
||||||
|
const mode = statSync(p).mode
|
||||||
|
if ((mode & 0o111) !== 0) continue
|
||||||
|
chmodSync(p, mode | 0o111)
|
||||||
|
} catch {
|
||||||
|
// ignore: 可能无权限(例如 /Applications 下 root-owned 的 .app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureExecutableBitsWithElevation(paths: string[], timeoutMs: number): Promise<void> {
|
||||||
|
const existing = paths.filter(p => existsSync(p))
|
||||||
|
if (existing.length === 0) return
|
||||||
|
|
||||||
|
const quotedPaths = existing.map(p => this.shellSingleQuote(p)).join(' ')
|
||||||
|
const timeoutSec = Math.max(30, Math.ceil(timeoutMs / 1000))
|
||||||
|
const scriptLines = [
|
||||||
|
`set chmodCmd to "/bin/chmod +x ${quotedPaths}"`,
|
||||||
|
`set timeoutSec to ${timeoutSec}`,
|
||||||
|
'with timeout of timeoutSec seconds',
|
||||||
|
'do shell script chmodCmd with administrator privileges',
|
||||||
|
'end timeout'
|
||||||
|
]
|
||||||
|
|
||||||
|
await execFileAsync('/usr/bin/osascript', scriptLines.flatMap(line => ['-e', line]), {
|
||||||
|
timeout: timeoutMs + 10_000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private async getDbKeyByHelperElevated(
|
private async getDbKeyByHelperElevated(
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
onStatus?: (message: string, level: number) => void
|
onStatus?: (message: string, level: number) => void
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const helperPath = this.getHelperPath()
|
const helperPath = this.getHelperPath()
|
||||||
|
const artifactPaths = this.collectMacKeyArtifactPaths(helperPath)
|
||||||
|
this.ensureExecutableBitsBestEffort(artifactPaths)
|
||||||
const waitMs = Math.max(timeoutMs, 30_000)
|
const waitMs = Math.max(timeoutMs, 30_000)
|
||||||
const timeoutSec = Math.ceil(waitMs / 1000) + 30
|
const timeoutSec = Math.ceil(waitMs / 1000) + 30
|
||||||
const pid = await this.getWeChatPid()
|
const pid = await this.getWeChatPid()
|
||||||
|
const chmodPart = artifactPaths.length > 0
|
||||||
|
? `/bin/chmod +x ${artifactPaths.map(p => this.shellSingleQuote(p)).join(' ')}`
|
||||||
|
: ''
|
||||||
|
const runPart = `${this.shellSingleQuote(helperPath)} ${pid} ${waitMs}`
|
||||||
|
const privilegedCmd = chmodPart ? `${chmodPart} && ${runPart}` : runPart
|
||||||
// 用 AppleScript 的 quoted form 组装命令,避免复杂 shell 拼接导致整条失败
|
// 用 AppleScript 的 quoted form 组装命令,避免复杂 shell 拼接导致整条失败
|
||||||
// 通过 try/on error 回传详细错误,避免只看到 "Command failed"
|
// 通过 try/on error 回传详细错误,避免只看到 "Command failed"
|
||||||
const scriptLines = [
|
const scriptLines = [
|
||||||
`set helperPath to ${JSON.stringify(helperPath)}`,
|
`set cmd to ${JSON.stringify(privilegedCmd)}`,
|
||||||
`set cmd to quoted form of helperPath & " ${pid} ${waitMs}"`,
|
|
||||||
`set timeoutSec to ${timeoutSec}`,
|
`set timeoutSec to ${timeoutSec}`,
|
||||||
'try',
|
'try',
|
||||||
'with timeout of timeoutSec seconds',
|
'with timeout of timeoutSec seconds',
|
||||||
@@ -751,10 +803,12 @@ export class KeyServiceMac {
|
|||||||
try {
|
try {
|
||||||
const helperPath = this.getImageScanHelperPath()
|
const helperPath = this.getImageScanHelperPath()
|
||||||
const ciphertextHex = ciphertext.toString('hex')
|
const ciphertextHex = ciphertext.toString('hex')
|
||||||
|
const artifactPaths = this.collectMacKeyArtifactPaths(helperPath)
|
||||||
|
this.ensureExecutableBitsBestEffort(artifactPaths)
|
||||||
|
|
||||||
// 1) 直接运行 helper(有正式签名的 debugger entitlement 时可用)
|
// 1) 直接运行 helper(有正式签名的 debugger entitlement 时可用)
|
||||||
if (!this._needsElevation) {
|
if (!this._needsElevation) {
|
||||||
const direct = await this._spawnScanHelper(helperPath, pid, ciphertextHex, false)
|
const direct = await this._spawnScanHelper(helperPath, pid, ciphertextHex, false, artifactPaths)
|
||||||
if (direct.key) return direct.key
|
if (direct.key) return direct.key
|
||||||
if (direct.permissionError) {
|
if (direct.permissionError) {
|
||||||
console.warn('[KeyServiceMac] task_for_pid 权限不足,切换到 osascript 提权模式')
|
console.warn('[KeyServiceMac] task_for_pid 权限不足,切换到 osascript 提权模式')
|
||||||
@@ -765,7 +819,12 @@ export class KeyServiceMac {
|
|||||||
|
|
||||||
// 2) 通过 osascript 以管理员权限运行 helper(SIP 下 ad-hoc 签名无法获取 task_for_pid)
|
// 2) 通过 osascript 以管理员权限运行 helper(SIP 下 ad-hoc 签名无法获取 task_for_pid)
|
||||||
if (this._needsElevation) {
|
if (this._needsElevation) {
|
||||||
const elevated = await this._spawnScanHelper(helperPath, pid, ciphertextHex, true)
|
try {
|
||||||
|
await this.ensureExecutableBitsWithElevation(artifactPaths, 45_000)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.warn('[KeyServiceMac] elevated chmod failed before image scan:', e?.message || e)
|
||||||
|
}
|
||||||
|
const elevated = await this._spawnScanHelper(helperPath, pid, ciphertextHex, true, artifactPaths)
|
||||||
if (elevated.key) return elevated.key
|
if (elevated.key) return elevated.key
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -868,12 +927,19 @@ export class KeyServiceMac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _spawnScanHelper(
|
private _spawnScanHelper(
|
||||||
helperPath: string, pid: number, ciphertextHex: string, elevated: boolean
|
helperPath: string,
|
||||||
|
pid: number,
|
||||||
|
ciphertextHex: string,
|
||||||
|
elevated: boolean,
|
||||||
|
artifactPaths: string[] = []
|
||||||
): Promise<{ key: string | null; permissionError: boolean }> {
|
): Promise<{ key: string | null; permissionError: boolean }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let child: ReturnType<typeof spawn>
|
let child: ReturnType<typeof spawn>
|
||||||
if (elevated) {
|
if (elevated) {
|
||||||
const shellCmd = `'${helperPath}' ${pid} ${ciphertextHex}`
|
const chmodPart = artifactPaths.length > 0
|
||||||
|
? `/bin/chmod +x ${artifactPaths.map(p => this.shellSingleQuote(p)).join(' ')} && `
|
||||||
|
: ''
|
||||||
|
const shellCmd = `${chmodPart}${this.shellSingleQuote(helperPath)} ${pid} ${ciphertextHex}`
|
||||||
child = spawn('/usr/bin/osascript', ['-e', `do shell script ${JSON.stringify(shellCmd)} with administrator privileges`],
|
child = spawn('/usr/bin/osascript', ['-e', `do shell script ${JSON.stringify(shellCmd)} with administrator privileges`],
|
||||||
{ stdio: ['ignore', 'pipe', 'pipe'] })
|
{ stdio: ['ignore', 'pipe', 'pipe'] })
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -115,12 +115,14 @@ export async function showNotification(data: any) {
|
|||||||
// 检查会话过滤
|
// 检查会话过滤
|
||||||
const filterMode = config.get("notificationFilterMode") || "all";
|
const filterMode = config.get("notificationFilterMode") || "all";
|
||||||
const filterList = config.get("notificationFilterList") || [];
|
const filterList = config.get("notificationFilterList") || [];
|
||||||
const sessionId = data.sessionId;
|
const sessionId = typeof data.sessionId === "string" ? data.sessionId : "";
|
||||||
|
// 系统通知(如 "WeFlow 准备就绪")不是聊天消息,不应受会话白/黑名单影响
|
||||||
|
const isSystemNotification = sessionId.startsWith("weflow-");
|
||||||
|
|
||||||
if (sessionId && filterMode !== "all" && filterList.length > 0) {
|
if (!isSystemNotification && filterMode !== "all") {
|
||||||
const isInList = filterList.includes(sessionId);
|
const isInList = sessionId !== "" && filterList.includes(sessionId);
|
||||||
if (filterMode === "whitelist" && !isInList) {
|
if (filterMode === "whitelist" && !isInList) {
|
||||||
// 白名单模式:不在列表中则不显示
|
// 白名单模式:不在列表中则不显示(空列表视为全部拦截)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (filterMode === "blacklist" && isInList) {
|
if (filterMode === "blacklist" && isInList) {
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -20,7 +20,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jieba-wasm": "^2.2.0",
|
"jieba-wasm": "^2.2.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"koffi": "^2.15.6",
|
"koffi": "^2.9.0",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
@@ -6537,9 +6537,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/koffi": {
|
"node_modules/koffi": {
|
||||||
"version": "2.15.6",
|
"version": "2.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.6.tgz",
|
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.2.tgz",
|
||||||
"integrity": "sha512-WQBpM5uo74UQ17UpsFN+PUOrQQg4/nYdey4SGVluQun2drYYfePziLLWdSmFb4wSdWlJC1aimXQnjhPCheRKuw==",
|
"integrity": "sha512-r9tjJLVRSOhCRWdVyQlF3/Ugzeg13jlzS4czS82MAgLff4W+BcYOW7g8Y62t9O5JYjYOLAjAovAZDNlDfZNu+g==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jieba-wasm": "^2.2.0",
|
"jieba-wasm": "^2.2.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"koffi": "^2.15.6",
|
"koffi": "^2.9.0",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
|
|
||||||
setHttpApiToken(token)
|
setHttpApiToken(token)
|
||||||
await configService.setHttpApiToken(token)
|
await configService.setHttpApiToken(token)
|
||||||
showMessage('已生成<EFBFBD><EFBFBD>保存新的 Access Token', true)
|
showMessage('已生成并保存新的 Access Token', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearApiToken = async () => {
|
const clearApiToken = async () => {
|
||||||
@@ -618,7 +618,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
showMessage(`已切换到${channelLabel}更新渠道,正在检查更新`, true)
|
showMessage(`已切换到${channelLabel}更新渠道,正在检查更新`, true)
|
||||||
await handleCheckUpdate()
|
await handleCheckUpdate()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
showMessage(`切换更新渠道<EFBFBD><EFBFBD>败: ${e}`, false)
|
showMessage(`切换更新渠道失败: ${e}`, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,7 +1213,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
if (result.success && result.aesKey) {
|
if (result.success && result.aesKey) {
|
||||||
if (typeof result.xorKey === 'number') setImageXorKey(`0x${result.xorKey.toString(16).toUpperCase().padStart(2, '0')}`)
|
if (typeof result.xorKey === 'number') setImageXorKey(`0x${result.xorKey.toString(16).toUpperCase().padStart(2, '0')}`)
|
||||||
setImageAesKey(result.aesKey)
|
setImageAesKey(result.aesKey)
|
||||||
setImageKeyStatus('已获取图片<EFBFBD><EFBFBD>钥')
|
setImageKeyStatus('已获取图片密钥')
|
||||||
showMessage('已自动获取图片密钥', true)
|
showMessage('已自动获取图片密钥', true)
|
||||||
const newXorKey = typeof result.xorKey === 'number' ? result.xorKey : 0
|
const newXorKey = typeof result.xorKey === 'number' ? result.xorKey : 0
|
||||||
const newAesKey = result.aesKey
|
const newAesKey = result.aesKey
|
||||||
@@ -2872,7 +2872,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
type="password"
|
type="password"
|
||||||
className="field-input"
|
className="field-input"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder="110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw"
|
placeholder="在此处填入你的 Telegram Bot Token"
|
||||||
value={aiInsightTelegramToken}
|
value={aiInsightTelegramToken}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const val = e.target.value
|
const val = e.target.value
|
||||||
@@ -3207,7 +3207,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
value={`http://${httpApiHost}:${httpApiPort}`}
|
value={`http://${httpApiHost}:${httpApiPort}`}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<button className="btn btn-secondary" onClick={handleCopyApiUrl} title="复<EFBFBD><EFBFBD><EFBFBD>">
|
<button className="btn btn-secondary" onClick={handleCopyApiUrl} title="复制">
|
||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -3341,7 +3341,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
try {
|
try {
|
||||||
const verifyResult = await window.electronAPI.auth.hello('请验证您的身份以开启 Windows Hello')
|
const verifyResult = await window.electronAPI.auth.hello('请验证您的身份以开启 Windows Hello')
|
||||||
if (!verifyResult.success) {
|
if (!verifyResult.success) {
|
||||||
showMessage(verifyResult.error || 'Windows Hello <EFBFBD><EFBFBD>证失败', false)
|
showMessage(verifyResult.error || 'Windows Hello 验证失败', false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3573,7 +3573,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
onClick={handleSetupHello}
|
onClick={handleSetupHello}
|
||||||
disabled={!helloAvailable || isSettingHello || !authEnabled || !helloPassword}
|
disabled={!helloAvailable || isSettingHello || !authEnabled || !helloPassword}
|
||||||
>
|
>
|
||||||
{isSettingHello ? '<EFBFBD><EFBFBD><EFBFBD>置中...' : '开启与设置'}
|
{isSettingHello ? '配置中...' : '开启与设置'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1127,7 +1127,7 @@ export default function SnsPage() {
|
|||||||
activeContactsCountTaskIdRef.current = null
|
activeContactsCountTaskIdRef.current = null
|
||||||
}
|
}
|
||||||
finishBackgroundTask(taskId, 'completed', {
|
finishBackgroundTask(taskId, 'completed', {
|
||||||
detail: '鑱旂郴浜烘湅鍙嬪湀鏉℃暟琛ョ畻瀹屾垚',
|
detail: '联系人朋友圈条数补算完成',
|
||||||
progressText: `${totalTargets}/${totalTargets}`
|
progressText: `${totalTargets}/${totalTargets}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ export async function setExportDefaultTxtColumns(columns: string[]): Promise<voi
|
|||||||
await config.set(CONFIG_KEYS.EXPORT_DEFAULT_TXT_COLUMNS, columns)
|
await config.set(CONFIG_KEYS.EXPORT_DEFAULT_TXT_COLUMNS, columns)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取导出默认并发<EFBFBD><EFBFBD>
|
// 获取导出默认并发数
|
||||||
export async function getExportDefaultConcurrency(): Promise<number | null> {
|
export async function getExportDefaultConcurrency(): Promise<number | null> {
|
||||||
const value = await config.get(CONFIG_KEYS.EXPORT_DEFAULT_CONCURRENCY)
|
const value = await config.get(CONFIG_KEYS.EXPORT_DEFAULT_CONCURRENCY)
|
||||||
if (typeof value === 'number' && Number.isFinite(value)) return value
|
if (typeof value === 'number' && Number.isFinite(value)) return value
|
||||||
|
|||||||
Reference in New Issue
Block a user