mirror of
https://github.com/a244573118/WeChatIntercept.git
synced 2026-06-14 05:25:08 +08:00
拦截到撤回时弹出 macOS 系统通知(显示谁撤回了消息)
This commit is contained in:
parent
fe51b1f1fe
commit
faf7db0482
70
README.md
70
README.md
|
|
@ -1,29 +1,62 @@
|
||||||
# WeChatIntercept
|
# WeChatIntercept
|
||||||
|
|
||||||
macOS 微信防撤回工具。
|
macOS 微信防撤回工具。
|
||||||
|
|
||||||
## 最新版本(v4.1.10)
|
## 最新版本
|
||||||
|
|
||||||
**支持微信 4.1.9及以上**,适配微信全新 C++ 架构,通过 DYLD 运行时注入实现防撤回,一键生效。
|
**支持微信 4.1.9 ~ 4.1.10**,适配微信全新 C++ 架构,通过 DYLD 运行时注入实现防撤回。
|
||||||
|
|
||||||
|
### 功能
|
||||||
|
|
||||||
|
- 对方撤回的消息保留可见
|
||||||
|
- 自己撤回正常工作
|
||||||
|
- 撤回时弹出 macOS 系统通知(显示谁撤回了消息)
|
||||||
|
- 通知开关可随时切换
|
||||||
|
|
||||||
### 原理
|
### 原理
|
||||||
|
|
||||||
通过注入一个运行时 hook 动态库(`WeChatAntiRevoke.dylib`),利用微信内建的 hook dispatch slot 机制拦截 `isRevokeMessage()` 函数。
|
通过注入运行时 hook 动态库(`WeChatAntiRevoke.dylib`),拦截微信内部的 `isRevokeMessage()` 函数:
|
||||||
|
|
||||||
|
- 对方撤回 → 返回 false(消息保留)+ 弹出通知
|
||||||
|
- 自己撤回 → 返回 true(正常处理)
|
||||||
|
|
||||||
|
通过读取当前登录用户 ID(完整字符串匹配),精确区分自己与对方。
|
||||||
|
|
||||||
### 适用范围
|
### 适用范围
|
||||||
|
|
||||||
- macOS 微信 4.1.9及以上
|
- macOS 微信 4.1.9、4.1.10
|
||||||
- Apple Silicon(arm64)及 Intel(x86_64)
|
- Apple Silicon(arm64)及 Intel(x86_64)
|
||||||
|
- macOS Sequoia / Sonoma / Ventura 等(自动处理 provenance 限制)
|
||||||
|
|
||||||
### 使用
|
### 使用
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x patch.sh # 添加可执行权限
|
chmod +x patch.sh # 添加可执行权限
|
||||||
./patch.sh # 安装防撤回
|
./patch.sh # 安装防撤回
|
||||||
./patch.sh --uninstall # 卸载
|
./patch.sh openNotify # 开启撤回通知
|
||||||
./patch.sh --help # 帮助
|
./patch.sh closeNotify # 关闭撤回通知
|
||||||
|
./patch.sh --uninstall # 卸载
|
||||||
|
./patch.sh --help # 帮助
|
||||||
```
|
```
|
||||||
|
|
||||||
首次运行可能需要约 30 秒(自动解除系统文件保护)。
|
首次运行可能需要约 30 秒(自动解除系统文件保护并重签名)。
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
配置文件路径:`~/.config/antirevoke/config`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
notify=1 # 1=开启撤回通知, 0=关闭
|
||||||
|
```
|
||||||
|
|
||||||
|
安装时默认开启通知,也可通过命令随时切换:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./patch.sh openNotify # 等同于设置 notify=1
|
||||||
|
./patch.sh closeNotify # 等同于设置 notify=0
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后立即生效,无需重启微信。
|
||||||
|
|
||||||
### 依赖
|
### 依赖
|
||||||
|
|
||||||
|
|
@ -33,17 +66,26 @@ macOS 系统自带工具,无需额外安装:
|
||||||
- codesign
|
- codesign
|
||||||
- tar
|
- tar
|
||||||
|
|
||||||
如未安装 Xcode Command Line Tools,运行:xcode-select --install
|
如未安装 Xcode Command Line Tools,运行:`xcode-select --install`
|
||||||
|
|
||||||
|
### 调试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./patch.sh --debug # 调试模式(不安装 hook,仅签名允许 lldb attach)
|
||||||
|
cat /tmp/antirevoke_debug.log # 查看运行时日志
|
||||||
|
```
|
||||||
|
|
||||||
### 已知限制
|
### 已知限制
|
||||||
|
|
||||||
- **无撤回提示**:当前方案仅静默保留原消息,不会在聊天窗口中显示"对方撤回了一条消息"的提示。你不会知道对方曾经尝试撤回,只能注意到消息没有消失。
|
- **聊天框内无撤回提示**:由于微信 4.x 的架构限制(C++ 实现 + 符号 strip + 数据库加密),无法在聊天界面内插入系统消息。替代方案为 macOS 系统通知。
|
||||||
|
|
||||||
- **为什么不能像旧版那样在聊天框内显示提示?**
|
- **为什么不能像旧版那样在聊天框内显示提示?**
|
||||||
|
|
||||||
旧版微信 macOS(3.x)使用 Objective-C 构建,核心逻辑暴露为 ObjC 方法,可以通过 Method Swizzling 在运行时拦截撤回处理函数,保留原消息的同时调用微信内部的消息插入 API 写入一条提示。
|
旧版微信 macOS(3.x)使用 Objective-C,可通过 Method Swizzling 调用内部消息插入 API。4.x 版本核心逻辑全部迁移到 C++(仅剩 65 个 ObjC 类,90MB+ 代码段,符号已 strip),撤回处理通过虚函数 + 加密数据库 + 协程调度完成,无法稳定地从外部构造调用链插入消息。
|
||||||
|
|
||||||
4.1.9以上的底层架构已完全不同:核心逻辑迁移到 C++ 实现(仅剩 65 个 ObjC 类,而代码段超过 90MB 均为 C++ 且符号已 strip)。撤回处理不再是独立的"删除旧消息"+"插入提示"两步操作,而是将整个消息对象替换为新的视图模型。在纯二进制补丁方式下,无法构造复杂的函数调用链来插入一条新消息到聊天记录中。
|
### 技术文档
|
||||||
|
|
||||||
|
详细的逆向分析过程和适配指南见 [`doc/reverse-engineering-guide.md`](doc/reverse-engineering-guide.md)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -87,5 +129,5 @@ macOS 系统自带工具,无需额外安装:
|
||||||
|
|
||||||
## 注意
|
## 注意
|
||||||
|
|
||||||
- 微信更新后需重新运行对应版本的补丁
|
- 微信更新后需重新运行 `./patch.sh`
|
||||||
- 仅供学习研究用途
|
- 仅供学习研究用途
|
||||||
|
|
|
||||||
235
patch.sh
235
patch.sh
|
|
@ -109,8 +109,9 @@ compile_dylib() {
|
||||||
#import <stdint.h>
|
#import <stdint.h>
|
||||||
#import <string.h>
|
#import <string.h>
|
||||||
#import <stdio.h>
|
#import <stdio.h>
|
||||||
|
#import <sys/stat.h>
|
||||||
|
|
||||||
// ── 日志(写入 /tmp/antirevoke_debug.log)────────────────────
|
// ── 日志 ─────────────────────────────────────────────────────
|
||||||
static FILE *g_logFile = NULL;
|
static FILE *g_logFile = NULL;
|
||||||
|
|
||||||
static void log_open(void) {
|
static void log_open(void) {
|
||||||
|
|
@ -121,26 +122,24 @@ static void log_open(void) {
|
||||||
if (g_logFile) { fprintf(g_logFile, "[AntiRevoke] " fmt "\n", ##__VA_ARGS__); fflush(g_logFile); } \
|
if (g_logFile) { fprintf(g_logFile, "[AntiRevoke] " fmt "\n", ##__VA_ARGS__); fflush(g_logFile); } \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
// ── 公共常量 ────────────────────────────────────────────────
|
// ── 常量 ─────────────────────────────────────────────────────
|
||||||
static const char *kDylibSuffix_Resources = "Resources/wechat.dylib";
|
static const char *kDylibSuffix_Resources = "Resources/wechat.dylib";
|
||||||
static const char *kDylibSuffix_Frameworks = "Frameworks/wechat.dylib";
|
static const char *kDylibSuffix_Frameworks = "Frameworks/wechat.dylib";
|
||||||
static const int32_t kRevokeType = 0x2712; // 10002
|
static const int32_t kRevokeType = 0x2712; // 10002
|
||||||
|
|
||||||
// ── 版本地址表 ───────────────────────────────────────────────
|
// 配置文件路径:~/.config/antirevoke/config
|
||||||
// 4.1.9 (CFBundleVersion 268602)
|
// 格式:每行一个 key=value
|
||||||
// arm64: hook dispatch slot VA = 0x9301838 (BSS,运行时可写)
|
// notify=1 开启通知(默认)
|
||||||
// x86_64: 暂不需要 slot(x86_64 直接 inline patch,同 4.1.10)
|
// notify=0 关闭通知
|
||||||
static const uintptr_t k419_SlotVA_arm64 = 0x9301838;
|
static char g_config_path[512] = {0};
|
||||||
|
|
||||||
// 4.1.10 (CFBundleVersion 268824) — dispatch slot 已移除,改用 inline trampoline
|
// ── 版本地址表 ───────────────────────────────────────────────
|
||||||
|
static const uintptr_t k419_SlotVA_arm64 = 0x9301838;
|
||||||
static const uintptr_t k4110_FuncVA_arm64 = 0x44FFE20;
|
static const uintptr_t k4110_FuncVA_arm64 = 0x44FFE20;
|
||||||
static const uintptr_t k4110_FuncVA_x86_64 = 0x4B4E9A0;
|
static const uintptr_t k4110_FuncVA_x86_64 = 0x4B4E9A0;
|
||||||
|
|
||||||
// 4.1.9 x86_64 函数 VA(inline trampoline,与 4.1.10 流程相同)
|
|
||||||
static const uintptr_t k419_FuncVA_x86_64 = 0x4AF08D0;
|
static const uintptr_t k419_FuncVA_x86_64 = 0x4AF08D0;
|
||||||
|
|
||||||
// ── 获取当前登录用户 ID(完整字符串)─────────────────────────
|
// ── 获取当前登录用户 ID ──────────────────────────────────────
|
||||||
// 懒加载:首次遇到撤回消息时从 app_data/login/ 读取最近登录的用户目录名
|
|
||||||
static char g_my_id[64] = {0};
|
static char g_my_id[64] = {0};
|
||||||
static _Bool g_my_id_loaded = 0;
|
static _Bool g_my_id_loaded = 0;
|
||||||
|
|
||||||
|
|
@ -159,10 +158,7 @@ static void load_my_user_id(void) {
|
||||||
NSFileManager *fm = [NSFileManager defaultManager];
|
NSFileManager *fm = [NSFileManager defaultManager];
|
||||||
NSString *dirPath = [NSString stringWithUTF8String:loginDir];
|
NSString *dirPath = [NSString stringWithUTF8String:loginDir];
|
||||||
NSArray *contents = [fm contentsOfDirectoryAtPath:dirPath error:nil];
|
NSArray *contents = [fm contentsOfDirectoryAtPath:dirPath error:nil];
|
||||||
if (!contents || [contents count] == 0) {
|
if (!contents || [contents count] == 0) return;
|
||||||
ARLOG("WARN: login 目录为空或不存在");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *latestName = nil;
|
NSString *latestName = nil;
|
||||||
NSDate *latestDate = nil;
|
NSDate *latestDate = nil;
|
||||||
|
|
@ -187,14 +183,57 @@ static void load_my_user_id(void) {
|
||||||
|
|
||||||
if (latestName && [latestName length] >= 3 && [latestName length] < sizeof(g_my_id)) {
|
if (latestName && [latestName length] >= 3 && [latestName length] < sizeof(g_my_id)) {
|
||||||
strncpy(g_my_id, [latestName UTF8String], sizeof(g_my_id) - 1);
|
strncpy(g_my_id, [latestName UTF8String], sizeof(g_my_id) - 1);
|
||||||
ARLOG("当前用户 ID: %s", g_my_id);
|
ARLOG("用户: %s", g_my_id);
|
||||||
} else {
|
|
||||||
ARLOG("WARN: 未能获取当前用户 ID");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── hook 函数(所有版本共用)────────────────────────────────
|
// ── 配置 ─────────────────────────────────────────────────────
|
||||||
|
static void init_config_path(void) {
|
||||||
|
const char *home = getenv("HOME");
|
||||||
|
if (home) {
|
||||||
|
snprintf(g_config_path, sizeof(g_config_path), "%s/.config/antirevoke/config", home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Bool is_notify_enabled(void) {
|
||||||
|
if (g_config_path[0] == '\0') return 1; // 配置路径未初始化,默认开启
|
||||||
|
FILE *f = fopen(g_config_path, "r");
|
||||||
|
if (!f) return 1; // 配置文件不存在,默认开启
|
||||||
|
char line[128];
|
||||||
|
_Bool enabled = 1;
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
if (strncmp(line, "notify=0", 8) == 0) { enabled = 0; break; }
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_notification(const char *text) {
|
||||||
|
if (!is_notify_enabled()) return;
|
||||||
|
|
||||||
|
// 对英文双引号和反斜杠做转义
|
||||||
|
char *escaped = (char *)malloc(1024);
|
||||||
|
if (!escaped) return;
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; text[i] && j < 1022; i++) {
|
||||||
|
if (text[i] == '"' || text[i] == '\\') escaped[j++] = '\\';
|
||||||
|
escaped[j++] = text[i];
|
||||||
|
}
|
||||||
|
escaped[j] = '\0';
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||||
|
FILE *sf = fopen("/tmp/antirevoke_notify.scpt", "w");
|
||||||
|
if (sf) {
|
||||||
|
fprintf(sf, "display notification \"%s\" with title \"WeChatIntercept\"\n", escaped);
|
||||||
|
fclose(sf);
|
||||||
|
system("osascript /tmp/antirevoke_notify.scpt");
|
||||||
|
}
|
||||||
|
free(escaped);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── hook 函数 ────────────────────────────────────────────────
|
||||||
__attribute__((visibility("default")))
|
__attribute__((visibility("default")))
|
||||||
_Bool hook_isRevokeMessage(void *msg) {
|
_Bool hook_isRevokeMessage(void *msg) {
|
||||||
if (msg == NULL) return 0;
|
if (msg == NULL) return 0;
|
||||||
|
|
@ -202,27 +241,46 @@ _Bool hook_isRevokeMessage(void *msg) {
|
||||||
int32_t msgType = *(int32_t *)((uint8_t *)msg + 0x0C);
|
int32_t msgType = *(int32_t *)((uint8_t *)msg + 0x0C);
|
||||||
if (msgType != kRevokeType) return 0;
|
if (msgType != kRevokeType) return 0;
|
||||||
|
|
||||||
// 懒加载当前用户 ID(首次遇到撤回消息时,登录一定已完成)
|
|
||||||
load_my_user_id();
|
load_my_user_id();
|
||||||
|
|
||||||
// msg+0x18: 撤回操作发起者的 ID(std::string SSO buffer,直接存储字符内容)
|
|
||||||
const char *sender = (const char *)((uint8_t *)msg + 0x18);
|
const char *sender = (const char *)((uint8_t *)msg + 0x18);
|
||||||
|
|
||||||
// 判断逻辑:
|
// 自己撤回 → 放行
|
||||||
// 1. field18 为空 → 自己撤回确认 → 放行
|
if (sender[0] == '\0') return 1;
|
||||||
// 2. field18 == 自己 ID → 自己撤回 → 放行
|
if (g_my_id[0] != '\0' && strncmp(sender, g_my_id, strlen(g_my_id)) == 0) return 1;
|
||||||
// 3. 其他 → 对方撤回 → 阻止
|
|
||||||
if (sender[0] == '\0') {
|
// 对方撤回 → 阻止
|
||||||
ARLOG("自己撤回(field=空),放行");
|
ARLOG("拦截: %.20s", sender);
|
||||||
return 1;
|
|
||||||
|
// 提取 replacemsg 并发通知
|
||||||
|
const char *xml_body = NULL;
|
||||||
|
uint64_t xml_ptr = *(uint64_t *)((uint8_t *)msg + 0x130);
|
||||||
|
uint64_t xml_len = *(uint64_t *)((uint8_t *)msg + 0x138);
|
||||||
|
if (xml_ptr != 0 && xml_len > 0 && xml_len < 4096)
|
||||||
|
xml_body = (const char *)xml_ptr;
|
||||||
|
|
||||||
|
char notify_text[256] = {0};
|
||||||
|
if (xml_body) {
|
||||||
|
const char *cs = strstr(xml_body, "<![CDATA[");
|
||||||
|
const char *ce = cs ? strstr(cs, "]]>") : NULL;
|
||||||
|
if (cs && ce) {
|
||||||
|
cs += 9;
|
||||||
|
size_t len = ce - cs;
|
||||||
|
if (len > 0 && len < sizeof(notify_text) - 1) {
|
||||||
|
memcpy(notify_text, cs, len);
|
||||||
|
notify_text[len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_my_id[0] != '\0' && strncmp(sender, g_my_id, strlen(g_my_id)) == 0) {
|
char content[512] = {0};
|
||||||
ARLOG("自己撤回(%s),放行", g_my_id);
|
if (notify_text[0] != '\0')
|
||||||
return 1;
|
snprintf(content, sizeof(content), "拦截到%s", notify_text);
|
||||||
}
|
else
|
||||||
|
snprintf(content, sizeof(content), "拦截到 %s 撤回了一条消息", sender);
|
||||||
|
|
||||||
|
send_notification(content);
|
||||||
|
|
||||||
ARLOG("对方撤回(%.20s),已阻止", sender);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,6 +367,7 @@ static void hook_init(void) {
|
||||||
dispatch_get_main_queue(), ^{
|
dispatch_get_main_queue(), ^{
|
||||||
|
|
||||||
log_open();
|
log_open();
|
||||||
|
init_config_path();
|
||||||
ARLOG("hook_init 启动");
|
ARLOG("hook_init 启动");
|
||||||
|
|
||||||
uintptr_t slide = find_wechat_slide();
|
uintptr_t slide = find_wechat_slide();
|
||||||
|
|
@ -615,6 +674,13 @@ do_install() {
|
||||||
resign_app
|
resign_app
|
||||||
verify_install
|
verify_install
|
||||||
|
|
||||||
|
# 创建默认配置(开启通知)
|
||||||
|
local CONFIG_DIR="$HOME/.config/antirevoke"
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
if [ ! -f "$CONFIG_DIR/config" ]; then
|
||||||
|
echo "notify=1" > "$CONFIG_DIR/config"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=============================="
|
echo "=============================="
|
||||||
echo " 安装成功!"
|
echo " 安装成功!"
|
||||||
|
|
@ -623,16 +689,71 @@ do_install() {
|
||||||
echo " 功能: 对方撤回的消息将保留可见"
|
echo " 功能: 对方撤回的消息将保留可见"
|
||||||
echo " 自己撤回消息正常工作"
|
echo " 自己撤回消息正常工作"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 验证步骤:"
|
echo " 通知开关:"
|
||||||
echo " 1. 让别人发一条消息,然后撤回"
|
echo " $0 openNotify 开启撤回通知"
|
||||||
echo " 2. 如果消息保留可见 → 防撤回生效"
|
echo " $0 closeNotify 关闭撤回通知"
|
||||||
echo " 3. 如果消息仍被撤回 → 执行以下命令查看调试日志:"
|
|
||||||
echo " cat /tmp/antirevoke_debug.log"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " 卸载: $0 --uninstall"
|
echo " 卸载: $0 --uninstall"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do_debug() {
|
||||||
|
print_banner
|
||||||
|
echo "[INFO] 调试模式(不安装 hook,仅签名允许 lldb attach)"
|
||||||
|
|
||||||
|
check_environment
|
||||||
|
kill_wechat
|
||||||
|
remove_provenance
|
||||||
|
|
||||||
|
# 删除已有的 hook dylib(确保无 hook)
|
||||||
|
rm -f "$DYLIB_DST" 2>/dev/null || true
|
||||||
|
|
||||||
|
# 签名(带 get-task-allow,允许 lldb attach)
|
||||||
|
echo "[INFO] 重签名(注入调试 entitlements)..."
|
||||||
|
local ENT_FILE=$(mktemp /tmp/entitlements_XXXXXX.plist)
|
||||||
|
cat > "$ENT_FILE" << 'ENTITLEMENTS'
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
ENTITLEMENTS
|
||||||
|
|
||||||
|
codesign --force --deep --sign - "$WECHAT_APP" 2>/dev/null
|
||||||
|
codesign --force --sign - --entitlements "$ENT_FILE" "$WECHAT_BIN" 2>/dev/null
|
||||||
|
xattr -cr "$WECHAT_APP" 2>/dev/null || true
|
||||||
|
rm -f "$ENT_FILE"
|
||||||
|
|
||||||
|
echo "[INFO] 启动微信..."
|
||||||
|
open "$WECHAT_APP"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=============================="
|
||||||
|
echo " 调试模式已启用"
|
||||||
|
echo "=============================="
|
||||||
|
echo ""
|
||||||
|
echo " 微信无 hook,撤回流程完整执行"
|
||||||
|
echo " 可使用 lldb attach 进行逆向分析"
|
||||||
|
echo ""
|
||||||
|
echo " 命令:"
|
||||||
|
echo " lldb -p \$(pgrep -x WeChat)"
|
||||||
|
echo " image list wechat.dylib"
|
||||||
|
echo " # Resources 行地址 = slide"
|
||||||
|
echo " br set -a <slide+0x4D5FD70>"
|
||||||
|
echo " c"
|
||||||
|
echo ""
|
||||||
|
echo " 恢复防撤回: $0"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
do_uninstall() {
|
do_uninstall() {
|
||||||
print_banner
|
print_banner
|
||||||
echo "[INFO] 卸载防撤回插件..."
|
echo "[INFO] 卸载防撤回插件..."
|
||||||
|
|
@ -659,8 +780,40 @@ do_uninstall() {
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CONFIG_DIR="$HOME/.config/antirevoke"
|
||||||
|
CONFIG_FILE="$CONFIG_DIR/config"
|
||||||
|
|
||||||
|
do_open_notify() {
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
if grep -q "^notify=" "$CONFIG_FILE" 2>/dev/null; then
|
||||||
|
sed -i '' 's/^notify=.*/notify=1/' "$CONFIG_FILE"
|
||||||
|
else
|
||||||
|
echo "notify=1" >> "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
echo "[INFO] 撤回通知已开启"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_close_notify() {
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
if grep -q "^notify=" "$CONFIG_FILE" 2>/dev/null; then
|
||||||
|
sed -i '' 's/^notify=.*/notify=0/' "$CONFIG_FILE"
|
||||||
|
else
|
||||||
|
echo "notify=0" >> "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
echo "[INFO] 撤回通知已关闭"
|
||||||
|
}
|
||||||
|
|
||||||
# ======================== 入口 ========================
|
# ======================== 入口 ========================
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
|
openNotify)
|
||||||
|
do_open_notify
|
||||||
|
;;
|
||||||
|
closeNotify)
|
||||||
|
do_close_notify
|
||||||
|
;;
|
||||||
|
--debug|-d)
|
||||||
|
do_debug
|
||||||
|
;;
|
||||||
--uninstall|-u)
|
--uninstall|-u)
|
||||||
do_uninstall
|
do_uninstall
|
||||||
;;
|
;;
|
||||||
|
|
@ -668,6 +821,9 @@ case "${1:-}" in
|
||||||
print_banner
|
print_banner
|
||||||
echo "用法:"
|
echo "用法:"
|
||||||
echo " $0 安装防撤回"
|
echo " $0 安装防撤回"
|
||||||
|
echo " $0 openNotify 开启撤回通知"
|
||||||
|
echo " $0 closeNotify 关闭撤回通知"
|
||||||
|
echo " $0 --debug 调试模式(无 hook,允许 lldb)"
|
||||||
echo " $0 --uninstall 卸载"
|
echo " $0 --uninstall 卸载"
|
||||||
echo " $0 --help 帮助"
|
echo " $0 --help 帮助"
|
||||||
;;
|
;;
|
||||||
|
|
@ -676,6 +832,7 @@ case "${1:-}" in
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "[ERROR] 未知参数: $1"
|
echo "[ERROR] 未知参数: $1"
|
||||||
|
echo "用法: $0 [openNotify|closeNotify|--uninstall|--debug|--help]"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user