refactor: use async/await instead of promises

support both local and network config
This commit is contained in:
Sunny Young 2025-12-06 13:42:56 +08:00
parent 540f13b35b
commit 4b1bd7c2ab
6 changed files with 113 additions and 98 deletions

View File

@ -1,25 +1,15 @@
{
"object": {
"pins": [
{
"package": "PromiseKit",
"repositoryURL": "https://github.com/mxcl/PromiseKit",
"state": {
"branch": null,
"revision": "2bc44395edb4f8391902a9ff7c220471882a4d07",
"version": "8.2.0"
}
},
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "cdd0ef3755280949551dc26dee5de9ddeda89f54",
"version": "1.6.2"
}
"originHash" : "fc7f739a75c4c771c736e9ff1f765bbd53fb1f3c5beaa621fbc867f0d71964c4",
"pins" : [
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "cdd0ef3755280949551dc26dee5de9ddeda89f54",
"version" : "1.6.2"
}
]
},
"version": 1
}
],
"version" : 3
}

View File

@ -16,14 +16,12 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/mxcl/PromiseKit", from: "8.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.0")
],
targets: [
.executableTarget(
name: "WeChatTweak",
dependencies: [
"PromiseKit",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
)

View File

@ -5,8 +5,15 @@
## 功能
- [x] 阻止消息撤回
- [ ] 客户端无限多开
- 阻止消息撤回
- 阻止自动更新
- 客户端多开
## 安装
```bash
brew install sunnyyoung/tap/wechattweak
```
## 使用

View File

@ -5,7 +5,6 @@
//
import Foundation
import PromiseKit
import ArgumentParser
struct Command {
@ -15,46 +14,44 @@ struct Command {
var errorDescription: String? {
switch self {
case let .executing(command, error):
return "Execute command: \(command) failed: \(error)"
return "executing: \(command) error: \(error)"
}
}
}
static func patch(app: URL, config: Config) -> Promise<Void> {
print("------ Path ------")
return Promise { seal in
do {
seal.fulfill(try Patcher.patch(binary: app.appendingPathComponent("Contents/MacOS/WeChat"), config: config))
} catch {
seal.reject(error)
}
}
static func version(app: URL) async throws -> String? {
try await Command.execute(command: "defaults read \(app.appendingPathComponent("Contents/Info.plist").path) CFBundleVersion")
}
static func resign(app: URL) -> Promise<Void> {
print("------ Resign ------")
return firstly {
Command.execute(command: "codesign --remove-sign \(app.path)")
}.then {
Command.execute(command: "codesign --force --deep --sign - \(app.path)")
}.then {
Command.execute(command: "xattr -cr \(app.path)")
}
static func patch(app: URL, config: Config) async throws {
try Patcher.patch(binary: app.appendingPathComponent("Contents/MacOS/WeChat"), config: config)
}
private static func execute(command: String) -> Promise<Void> {
return Promise { seal in
print("Execute command: \(command)")
var error: NSDictionary?
guard let script = NSAppleScript(source: "do shell script \"\(command)\"") else {
return seal.reject(Error.executing(command: command, error: ["error": "Create script failed."]))
}
script.executeAndReturnError(&error)
if let error = error {
seal.reject(Error.executing(command: command, error: error))
} else {
seal.fulfill(())
}
static func resign(app: URL) async throws {
try await Command.execute(command: "codesign --remove-sign \(app.path)")
try await Command.execute(command: "codesign --force --deep --sign - \(app.path)")
try await Command.execute(command: "xattr -cr \(app.path)")
}
@discardableResult
private static func execute(command: String) async throws -> String? {
guard let script = NSAppleScript(source: "do shell script \"\(command)\"") else {
throw Error.executing(
command: command,
error: ["error": "Create script failed."]
)
}
var error: NSDictionary?
let descriptor = script.executeAndReturnError(&error)
if let error = error {
throw Error.executing(
command: command,
error: error
)
} else {
return descriptor.stringValue
}
}
}

View File

@ -80,6 +80,20 @@ struct Config: Decodable {
let version: String
let targets: [Target]
static func load(from url: URL) async throws -> [Config] {
if url.isFileURL {
return try JSONDecoder().decode(
[Config].self,
from: Data(contentsOf: url)
)
} else {
return try JSONDecoder().decode(
[Config].self,
from: try await URLSession.shared.data(from: url).0
)
}
}
}
private extension Data {

View File

@ -5,13 +5,14 @@
//
import Foundation
import PromiseKit
import Dispatch
import ArgumentParser
struct Patch: ParsableCommand {
struct Patch: AsyncParsableCommand {
enum Error: LocalizedError {
case invalidApp
case invalidConfig
case invalidVersion
case unsupportedVersion
var errorDescription: String? {
@ -20,6 +21,8 @@ struct Patch: ParsableCommand {
return "Invalid app path"
case .invalidConfig:
return "Invalid patch config"
case .invalidVersion:
return "Invalid app version"
case .unsupportedVersion:
return "Unsupported WeChat version"
}
@ -30,7 +33,7 @@ struct Patch: ParsableCommand {
@Option(
name: .shortAndLong,
help: "Default: /Applications/WeChat.app",
help: "Path of WeChat.app",
transform: {
guard FileManager.default.fileExists(atPath: $0) else {
throw Error.invalidApp
@ -42,55 +45,58 @@ struct Patch: ParsableCommand {
@Option(
name: .shortAndLong,
help: "Default: ./config.json",
help: "Local path or Remote URL of config.json",
transform: {
guard FileManager.default.fileExists(atPath: $0) else {
throw Error.invalidConfig
if FileManager.default.fileExists(atPath: $0) {
return URL(fileURLWithPath: $0)
} else {
guard let url = URL(string: $0) else {
throw Error.invalidConfig
}
return url
}
return URL(fileURLWithPath: $0)
}
)
var config: URL = {
var size: UInt32 = 0
_NSGetExecutablePath(nil, &size)
var buffer = [CChar](repeating: 0, count: Int(size))
guard _NSGetExecutablePath(&buffer, &size) == 0, let path = String(utf8String: buffer) else {
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
}
return URL(fileURLWithPath: path).resolvingSymlinksInPath().deletingLastPathComponent().appendingPathComponent("config.json")
}()
var config: URL = URL(string: "https://raw.githubusercontent.com/sunnyyoung/WeChatTweak/refs/heads/feature/2.0/config.json")!
func run() throws {
let configs = try JSONDecoder().decode([Config].self, from: Data(contentsOf: self.config))
mutating func run() async throws {
do {
print("------ Version ------")
guard let version = try await Command.version(app: self.app) else {
throw Error.invalidVersion
}
print("\(version)")
guard
let info = NSDictionary(contentsOf: self.app.appendingPathComponent("Contents/Info.plist")),
let version = info["CFBundleVersion"] as? String,
let config = configs.first(where: { $0.version == version })
else {
throw Error.unsupportedVersion
}
print("------ Config ------")
guard let config = (try await Config.load(from: self.config)).first(where: { $0.version == version }) else {
throw Error.unsupportedVersion
}
print("\(config)")
firstly {
Command.patch(
print("------ Patch ------")
try await Command.patch(
app: self.app,
config: config
)
}.then {
Command.resign(app: self.app)
}.ensure {
print("")
}.done {
print("🎉 Done!")
print("Done!")
print("------ Resign ------")
try await Command.resign(
app: self.app
)
print("Done!")
print("------🎉 Done!------")
Darwin.exit(EXIT_SUCCESS)
}.catch { error in
print("🚨 \(error.localizedDescription)", stderr)
} catch {
print("------🚨 Error------")
print("\(error.localizedDescription)")
Darwin.exit(EXIT_FAILURE)
}
}
}
struct Tweak: ParsableCommand {
struct Tweak: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "wechattweak",
abstract: "A command-line tool for tweaking WeChat.",
@ -101,5 +107,8 @@ struct Tweak: ParsableCommand {
)
}
Tweak.main()
CFRunLoopRun()
Task {
await Tweak.main()
}
Dispatch.dispatchMain()