diff --git a/Package.resolved b/Package.resolved index 906c3eb..d574855 100644 --- a/Package.resolved +++ b/Package.resolved @@ -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 } diff --git a/Package.swift b/Package.swift index 8b73cff..f01293c 100644 --- a/Package.swift +++ b/Package.swift @@ -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") ] ) diff --git a/README.md b/README.md index d2e4224..a9f94fd 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,15 @@ ## 功能 -- [x] 阻止消息撤回 -- [ ] 客户端无限多开 +- 阻止消息撤回 +- 阻止自动更新 +- 客户端多开 + +## 安装 + +```bash +brew install sunnyyoung/tap/wechattweak +``` ## 使用 diff --git a/Sources/WeChatTweak/Command.swift b/Sources/WeChatTweak/Command.swift index 97493f7..f0642b3 100644 --- a/Sources/WeChatTweak/Command.swift +++ b/Sources/WeChatTweak/Command.swift @@ -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 { - 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 { - 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 { - 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 } } } diff --git a/Sources/WeChatTweak/Config.swift b/Sources/WeChatTweak/Config.swift index 57ac6c4..7587bfc 100644 --- a/Sources/WeChatTweak/Config.swift +++ b/Sources/WeChatTweak/Config.swift @@ -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 { diff --git a/Sources/WeChatTweak/main.swift b/Sources/WeChatTweak/main.swift index a9be6d6..74f80c4 100644 --- a/Sources/WeChatTweak/main.swift +++ b/Sources/WeChatTweak/main.swift @@ -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()