diff --git a/.gitignore b/.gitignore index cd05f23..89396be 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ fastlane/FastlaneRunner .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* diff --git a/.swiftlint.yml b/.swiftlint.yml index 16751af..1e6db25 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -13,7 +13,6 @@ identifier_name: line_length: ignores_comments: true ignores_interpolated_strings: true - warning: 140 type_name: excluded: diff --git a/Example/NBus/AppDelegate.swift b/Example/NBus/AppDelegate.swift index 466f7c5..be2b092 100644 --- a/Example/NBus/AppDelegate.swift +++ b/Example/NBus/AppDelegate.swift @@ -9,6 +9,8 @@ import NBus import PinLayout import RxCocoa +import RxSwift +import SwiftTrace import UIKit @UIApplicationMain @@ -16,11 +18,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? + private let disposeBag = DisposeBag() + func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + clearStorage() + +// observeQQ() + AppState.shared.setup() let viewController = ViewController() @@ -41,6 +49,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { + logger.debug("\(url)") return Bus.shared.openURL(url) } @@ -49,6 +58,146 @@ class AppDelegate: UIResponder, UIApplicationDelegate { continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void ) -> Bool { + logger.debug("\(userActivity.webpageURL!)") return Bus.shared.openUserActivity(userActivity) } } + +extension AppDelegate { + + private func pasteboardItems() -> Observable<[[String]]> { + NotificationCenter.default.rx + .notification(UIPasteboard.changedNotification) + .map { _ -> [[String]] in + UIPasteboard.general.items.map { item -> [String] in + item.map { key, value -> String in + switch value { + case let data as Data: + if + let object = NSKeyedUnarchiver.unarchiveObject( + with: data + ) { + return "[Data-Keyed] \(key), \(object)" + } else if + let plist = try? PropertyListSerialization.propertyList( + from: data, + options: [], + format: nil + ) { + return "[Data-Plist] \(key), \(plist)" + } else if + let string = String( + data: data, + encoding: .utf8 + ) { + return "[Data-String] \(key), \(string)" + } else { + assertionFailure() + return "\(key), \(value)" + } + case let string as String: + return "[String] \(key), \(string)" + default: + assertionFailure() + return "\(key), \(value)" + } + } + } + } + .distinctUntilChanged() + } + + private func canOpenURL() -> Observable { + UIApplication.shared.rx + .methodInvoked(#selector(UIApplication.canOpenURL(_:))) + .compactMap { args in + args[0] as? URL + } + } + + private func openURL() -> Observable { + UIApplication.shared.rx + .methodInvoked(#selector(UIApplication.open(_:options:completionHandler:))) + .compactMap { args in + args[0] as? URL + } + } +} + +extension AppDelegate { + + private func observeSDK() { + pasteboardItems() + .delay(.seconds(1), scheduler: MainScheduler.instance) + .bind(onNext: { items in + logger.debug("\(items)") + }) + .disposed(by: disposeBag) + + canOpenURL() + .bind(onNext: { url in + logger.debug("\(url)") + }) + .disposed(by: disposeBag) + + openURL() + .bind(onNext: { url in + logger.debug("\(url)") + }) + .disposed(by: disposeBag) + } +} + +extension AppDelegate { + + private func clearKeychains() { + let items = [ + kSecClassGenericPassword, + kSecClassInternetPassword, + kSecClassCertificate, + kSecClassKey, + kSecClassIdentity, + ] + + let status = items + .map { [kSecClass: $0] as CFDictionary } + .map { SecItemDelete($0) } + + assert(status.allSatisfy { + $0 == errSecSuccess || $0 == errSecItemNotFound + }) + } + + private func clearPasteboard() { + let pasteboard = UIPasteboard.general + + pasteboard.items = [] + } + + private func clearUserDefaults() { + let defaults = UserDefaults.standard + + for (key, _) in defaults.dictionaryRepresentation() { + defaults.removeObject(forKey: key) + } + } +} + +extension AppDelegate { + + private func clearStorage() { + clearKeychains() + clearPasteboard() + clearUserDefaults() + } +} + +extension AppDelegate { + + private func observeQQ() { +// SwiftTrace.traceClasses(matchingPattern: "^QQ") +// SwiftTrace.traceClasses(matchingPattern: "^Tencent") + + observeSDK() + } +} diff --git a/Example/NBus/Config.xcconfig b/Example/NBus/Config.xcconfig index 5a86090..106ce08 100644 --- a/Example/NBus/Config.xcconfig +++ b/Example/NBus/Config.xcconfig @@ -13,6 +13,7 @@ SIMPLE_SLASH = / DOMAIN = www.nuomi1.com QQ_ID = 123456 +QQ_MID = 123456 WECHAT_ID = 123456 WECHAT_MID = 123456 WEIBO_ID = 123456 @@ -21,6 +22,7 @@ ASSOCIATED_DOMAIN = applinks:$(DOMAIN) DEVELOPMENT_TEAM = BUSMOCK PRODUCT_BUNDLE_IDENTIFIER = com.nuomi1.bus.mock QQ_APPID = tencent$(QQ_ID) +QQ_MINIPROGRAMID = $(QQ_MID) QQ_UNIVERSALLINK = https:$(SIMPLE_SLASH)/$(DOMAIN)/qq_conn/$(QQ_ID)/ WECHAT_APPID = wx$(WECHAT_ID) WECHAT_MINIPROGRAMID = gh_$(WECHAT_MID) diff --git a/Example/NBus/Info.plist b/Example/NBus/Info.plist index 7550fca..750dbd4 100644 --- a/Example/NBus/Info.plist +++ b/Example/NBus/Info.plist @@ -4,6 +4,12 @@ BMMiniProgramIDTypes + + BMMiniProgramID + ${QQ_MINIPROGRAMID} + BMPlatform + QQ + BMMiniProgramID ${WECHAT_MINIPROGRAMID} @@ -106,6 +112,12 @@ LSRequiresIPhoneOS + NSBonjourServices + + _adhp._tcp + + NSLocalNetworkUsageDescription + WoodPeckeriOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/Example/NBus/Model/AppState.swift b/Example/NBus/Model/AppState.swift index a9e0308..701e486 100644 --- a/Example/NBus/Model/AppState.swift +++ b/Example/NBus/Model/AppState.swift @@ -23,11 +23,20 @@ extension AppState { struct PlatformItem { let platform: Platform - let handler: HandlerType + let category: Category + let handlers: [Category: HandlerType] let viewController: () -> UIViewController } } +extension AppState.PlatformItem { + + enum Category: Hashable { + case bus + case sdk + } +} + extension AppState { func setup() { @@ -51,7 +60,10 @@ extension AppState { let wechatItem = AppState.PlatformItem( platform: Platforms.wechat, - handler: wechatSDKHandler, + category: .sdk, + handlers: [ + .sdk: wechatSDKHandler, + ], viewController: { PlatformViewController() } ) @@ -66,9 +78,18 @@ extension AppState { logger.debug("\(message)", file: file, function: function, line: line) } + let qqHandler = QQHandler( + appID: AppState.getAppID(for: Platforms.qq)!, + universalLink: AppState.getUniversalLink(for: Platforms.qq)! + ) + let qqItem = AppState.PlatformItem( platform: Platforms.qq, - handler: qqSDKHandler, + category: .sdk, + handlers: [ + .bus: qqHandler, + .sdk: qqSDKHandler, + ], viewController: { PlatformViewController() } ) @@ -86,7 +107,10 @@ extension AppState { let weiboItem = AppState.PlatformItem( platform: Platforms.weibo, - handler: weiboSDKHandler, + category: .sdk, + handlers: [ + .sdk: weiboSDKHandler, + ], viewController: { PlatformViewController() } ) @@ -100,7 +124,10 @@ extension AppState { let systemItem = AppState.PlatformItem( platform: Platforms.system, - handler: systemHandler, + category: .bus, + handlers: [ + .bus: systemHandler, + ], viewController: { PlatformViewController() } ) @@ -110,13 +137,6 @@ extension AppState { weiboItem, systemItem, ]) - - Bus.shared.handlers = [ - wechatSDKHandler, - qqSDKHandler, - weiboSDKHandler, - systemHandler, - ] } // swiftlint:enable function_body_length diff --git a/Example/NBus/Model/MediaSource.swift b/Example/NBus/Model/MediaSource.swift index 3482ac7..3899120 100644 --- a/Example/NBus/Model/MediaSource.swift +++ b/Example/NBus/Model/MediaSource.swift @@ -33,8 +33,12 @@ enum MediaSource { // https://giphy.com/gifs/bus-J1ZajKJKzD0PK let dataAsset = NSDataAsset(name: "giphy-J1ZajKJKzD0PK")! let data = dataAsset.data + let thumbnail = UIImage(data: data)?.jpegData(compressionQuality: 0.2) - return Messages.image(data: data) + return Messages.image( + data: data, + thumbnail: thumbnail + ) }() static let audio: MessageType = { @@ -86,16 +90,30 @@ enum MediaSource { let dataAsset = NSDataAsset(name: "giphy-J1ZajKJKzD0PK")! let data = dataAsset.data - let title = "J1ZajKJKzD0PK" + let fileName = "J1ZajKJKzD0PK" return Messages.file( data: data, fileExtension: "gif", - title: title + fileName: fileName + ) + }() + + static let qqMiniProgram: MessageType = { + let path = "/pages/component/pages/launchApp813/launchApp813?a=aaa&b=bbb&c=ccc" + let url = URL(string: "https://www.apple.com.cn/iphone/")! + + let miniProgramID = AppState.getMiniProgramID(for: Platforms.qq)! + + return Messages.miniProgram( + miniProgramID: miniProgramID, + path: path, + link: url, + miniProgramType: .release ) }() - static let miniProgram: MessageType = { + static let wechatMiniProgram: MessageType = { let path = "/pages/community/topics/id?id=565" let url = URL(string: "https://www.apple.com.cn/iphone/")! diff --git a/Example/NBus/View/PlatformViewController+Oauth.swift b/Example/NBus/View/PlatformViewController+Oauth.swift index db8b3ea..4ad9c99 100644 --- a/Example/NBus/View/PlatformViewController+Oauth.swift +++ b/Example/NBus/View/PlatformViewController+Oauth.swift @@ -89,7 +89,7 @@ extension PlatformViewController.OauthView { height: subview.intrinsicContentSize.height ) : CGSize( - width: superview.bounds.size.width - 2 * Constant.spacing, + width: max(0, superview.bounds.size.width - 2 * Constant.spacing), height: 5 * Constant.spacing ) }() diff --git a/Example/NBus/View/PlatformViewController+Share.swift b/Example/NBus/View/PlatformViewController+Share.swift index 88b4e10..ad8826e 100644 --- a/Example/NBus/View/PlatformViewController+Share.swift +++ b/Example/NBus/View/PlatformViewController+Share.swift @@ -221,7 +221,18 @@ extension PlatformViewController.ShareView { } private func didTapMiniProgramButton() { - share(MediaSource.miniProgram, in: miniProgramButton) + let message: MessageType + + switch viewModel?.platform.value { + case Platforms.wechat: + message = MediaSource.wechatMiniProgram + case Platforms.qq: + message = MediaSource.qqMiniProgram + default: + message = MediaSource.wechatMiniProgram + } + + share(message, in: miniProgramButton) } } diff --git a/Example/NBus/View/PlatformViewController+ViewModel.swift b/Example/NBus/View/PlatformViewController+ViewModel.swift index 1fae009..44471be 100644 --- a/Example/NBus/View/PlatformViewController+ViewModel.swift +++ b/Example/NBus/View/PlatformViewController+ViewModel.swift @@ -21,6 +21,10 @@ extension PlatformViewController { let title: Observable + let isSwitchEnabled: Observable + let currentCategory: BehaviorRelay + let currentHandler: Observable + let endpoints: Observable<[Endpoint]> let currentEndpoint: BehaviorRelay @@ -33,23 +37,25 @@ extension PlatformViewController { init(_ element: AppState.PlatformItem) { title = .just("\(element.platform)") - endpoints = .just(element.endpoints ?? []) + isSwitchEnabled = .just(element.handlers.count > 1) + currentCategory = .init(value: element.category) + currentHandler = currentCategory + .compactMap { element.handlers[$0] } + + endpoints = currentHandler + .compactMap { $0 as? ShareHandlerType } + .map { $0.endpoints } currentEndpoint = .init(value: nil) platform = .init(value: element.platform) oauthInfo = .init(value: Self.defaultOauthInfo) - isShareEnabled = .just(element.handler is ShareHandlerType) - isOauthEnabled = .just(element.handler is OauthHandlerType) + isShareEnabled = currentHandler + .map { $0 is ShareHandlerType } + isOauthEnabled = currentHandler + .map { $0 is OauthHandlerType } } static let defaultOauthInfo = OauthInfo(isLogin: false, parameter: "未登录") } } - -extension AppState.PlatformItem { - - fileprivate var endpoints: [Endpoint]? { - (handler as? ShareHandlerType)?.endpoints - } -} diff --git a/Example/NBus/View/PlatformViewController.swift b/Example/NBus/View/PlatformViewController.swift index eb63438..b56a453 100644 --- a/Example/NBus/View/PlatformViewController.swift +++ b/Example/NBus/View/PlatformViewController.swift @@ -29,6 +29,8 @@ class PlatformViewController: UIViewController { private let disposeBag = DisposeBag() + private let handlerBarButtonItem = UIBarButtonItem() + private let scrollView = UIScrollView() private let contentView = UIView() @@ -43,6 +45,7 @@ extension PlatformViewController { view.backgroundColor = .white + setupNavigationItem() setupSubviews() } @@ -75,6 +78,10 @@ extension PlatformViewController { scrollView.contentSize = contentView.bounds.size } + private func setupNavigationItem() { + navigationItem.rightBarButtonItem = handlerBarButtonItem + } + private func setupSubviews() { view.addSubview(scrollView) scrollView.addSubview(contentView) @@ -93,6 +100,42 @@ extension PlatformViewController { .bind(to: rx.title) .disposed(by: disposeBag) + viewModel.isSwitchEnabled + .bind(to: handlerBarButtonItem.rx.isEnabled) + .disposed(by: disposeBag) + + viewModel.currentCategory + .map { + switch $0 { + case .bus: + return "开源" + case .sdk: + return "闭源" + } + } + .bind(to: handlerBarButtonItem.rx.title) + .disposed(by: disposeBag) + + viewModel.currentHandler + .bind(onNext: { + Bus.shared.handlers = [$0] + }) + .disposed(by: disposeBag) + + handlerBarButtonItem.rx + .tap + .withLatestFrom(viewModel.currentCategory) + .map { + switch $0 { + case .bus: + return .sdk + case .sdk: + return .bus + } + } + .bind(to: viewModel.currentCategory) + .disposed(by: disposeBag) + shareView.contentView.binding(viewModel) shareView.contentView.onShare = { [weak self] message, endpoint, view in let options: [Bus.ShareOptionKey: Any] = { diff --git a/Example/Podfile b/Example/Podfile index d57dfa6..712638c 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -3,10 +3,13 @@ platform :ios, "10.0" use_frameworks! :linkage => :static target "BusMock" do - pod "NBus", :path => "../" + pod "NBus/BusHandlers", :path => "../" + pod "NBus/SDKHandlers", :path => "../" pod "PinLayout" pod "RxCocoa" + pod "SwiftTrace" + pod "WoodPeckeriOS" end post_install do |installer| diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 1bfd493..abee88a 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,38 +1,46 @@ PODS: - - NBus (0.6.0): - - NBus/SDKHandlers (= 0.6.0) - - NBus/Core (0.6.0) - - NBus/QQSDK (0.6.0) - - NBus/QQSDKHandler (0.6.0): + - NBus/BusHandlers (0.7.0): + - NBus/QQHandler + - NBus/SystemHandler + - NBus/Core (0.7.0) + - NBus/QQHandler (0.7.0): + - NBus/Core + - NBus/QQSDK (0.7.0) + - NBus/QQSDKHandler (0.7.0): - NBus/Core - NBus/QQSDK - - NBus/SDKHandlers (0.6.0): + - NBus/SDKHandlers (0.7.0): - NBus/QQSDKHandler - NBus/SystemHandler - NBus/WechatSDKHandler - NBus/WeiboSDKHandler - - NBus/SystemHandler (0.6.0): + - NBus/SystemHandler (0.7.0): - NBus/Core - - NBus/WechatSDK (0.6.0) - - NBus/WechatSDKHandler (0.6.0): + - NBus/WechatSDK (0.7.0) + - NBus/WechatSDKHandler (0.7.0): - NBus/Core - NBus/WechatSDK - - NBus/WeiboSDK (0.6.0) - - NBus/WeiboSDKHandler (0.6.0): + - NBus/WeiboSDK (0.7.0) + - NBus/WeiboSDKHandler (0.7.0): - NBus/Core - NBus/WeiboSDK - - PinLayout (1.9.2) - - RxCocoa (5.1.1): - - RxRelay (~> 5) - - RxSwift (~> 5) - - RxRelay (5.1.1): - - RxSwift (~> 5) - - RxSwift (5.1.1) + - PinLayout (1.9.3) + - RxCocoa (6.0.0): + - RxRelay (= 6.0.0) + - RxSwift (= 6.0.0) + - RxRelay (6.0.0): + - RxSwift (= 6.0.0) + - RxSwift (6.0.0) + - SwiftTrace (6.6.0) + - WoodPeckeriOS (1.2.9) DEPENDENCIES: - - NBus (from `../`) + - NBus/BusHandlers (from `../`) + - NBus/SDKHandlers (from `../`) - PinLayout - RxCocoa + - SwiftTrace + - WoodPeckeriOS SPEC REPOS: trunk: @@ -40,18 +48,22 @@ SPEC REPOS: - RxCocoa - RxRelay - RxSwift + - SwiftTrace + - WoodPeckeriOS EXTERNAL SOURCES: NBus: :path: "../" SPEC CHECKSUMS: - NBus: af1724058bfe5c1b70803657f1aa5add29c42007 - PinLayout: 0d96022e46e8b80468d819f182a393b97ca038da - RxCocoa: 32065309a38d29b5b0db858819b5bf9ef038b601 - RxRelay: d77f7d771495f43c556cbc43eebd1bb54d01e8e9 - RxSwift: 81470a2074fa8780320ea5fe4102807cb7118178 + NBus: 909effc8a33bd2e5e3549406dc8b718f028f1776 + PinLayout: 4d8733121f8687edcc8f00c19bf1379d12808767 + RxCocoa: 3f79328fafa3645b34600f37c31e64c73ae3a80e + RxRelay: 8d593be109c06ea850df027351beba614b012ffb + RxSwift: c14e798c59b9f6e9a2df8fd235602e85cc044295 + SwiftTrace: affc6601a316add7c299a3916d9f43c286a583ef + WoodPeckeriOS: f16864f7a78d5c6daa605591dd66690d466d95be -PODFILE CHECKSUM: 375fd17d41450a3e1f41c4b4dd1d218a2089309d +PODFILE CHECKSUM: 5bd2560c0821586df3f229f1739f98be39727940 COCOAPODS: 1.10.0 diff --git a/NBus.podspec b/NBus.podspec index d3d5b0e..a15d1a3 100644 --- a/NBus.podspec +++ b/NBus.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "NBus" - s.version = "0.7.0" + s.version = "0.8.0" s.summary = "A short description of NBus." s.homepage = "https://github.com/nuomi1/NBus" @@ -14,6 +14,11 @@ Pod::Spec.new do |s| s.default_subspecs = "SDKHandlers" + s.subspec "BusHandlers" do |ss| + ss.dependency "NBus/QQHandler" + ss.dependency "NBus/SystemHandler" + end + s.subspec "SDKHandlers" do |ss| ss.dependency "NBus/QQSDKHandler" ss.dependency "NBus/WechatSDKHandler" @@ -22,7 +27,7 @@ Pod::Spec.new do |s| end s.subspec "Core" do |ss| - ss.source_files = ["NBus/Classes/Core/**/*.swift"] + ss.source_files = Dir.glob("NBus/Classes/Core/**/*.swift") end s.subspec "QQSDKHandler" do |ss| @@ -32,6 +37,12 @@ Pod::Spec.new do |s| ss.source_files = ["NBus/Classes/Handler/QQSDKHandler.swift"] end + s.subspec "QQHandler" do |ss| + ss.dependency "NBus/Core" + + ss.source_files = ["NBus/Classes/Handler/QQHandler.swift"] + end + s.subspec "WechatSDKHandler" do |ss| ss.dependency "NBus/Core" ss.dependency "NBus/WechatSDK" @@ -53,31 +64,31 @@ Pod::Spec.new do |s| end s.subspec "QQSDK" do |ss| - ss.vendored_frameworks = ["NBus/Vendor/QQ_SDK/**/*.framework"] + ss.vendored_frameworks = Dir.glob("NBus/Vendor/QQ_SDK/**/*.framework") ss.frameworks = ["SystemConfiguration", "WebKit"] ss.source_files = Dir.glob("NBus/Vendor/QQ_SDK/**/*.h") .reject { |name| name.include?("TencentOpenApiUmbrellaHeader.h") } - ss.resources = ["NBus/Vendor/QQ_SDK/**/*.bundle"] + ss.resources = Dir.glob("NBus/Vendor/QQ_SDK/**/*.bundle") end s.subspec "WechatSDK" do |ss| - ss.vendored_libraries = ["NBus/Vendor/Wechat_SDK/**/*.a"] + ss.vendored_libraries = Dir.glob("NBus/Vendor/Wechat_SDK/**/*.a") ss.frameworks = ["WebKit"] ss.libraries = ["c++"] - ss.source_files = ["NBus/Vendor/Wechat_SDK/**/*.h"] + ss.source_files = Dir.glob("NBus/Vendor/Wechat_SDK/**/*.h") ss.pod_target_xcconfig = { "OTHER_LDFLAGS" => "-ObjC -all_load" } end s.subspec "WeiboSDK" do |ss| - ss.vendored_libraries = ["NBus/Vendor/Weibo_SDK/**/*.a"] + ss.vendored_libraries = Dir.glob("NBus/Vendor/Weibo_SDK/**/*.a") - ss.source_files = ["NBus/Vendor/Weibo_SDK/**/*.h"] + ss.source_files = Dir.glob("NBus/Vendor/Weibo_SDK/**/*.h") - ss.resources = ["NBus/Vendor/Weibo_SDK/**/*.bundle"] + ss.resources = Dir.glob("NBus/Vendor/Weibo_SDK/**/*.bundle") end s.prepare_command = <<-CMD diff --git a/NBus/Classes/Core/Bus+Error.swift b/NBus/Classes/Core/Bus+Error.swift index 522c75e..3f49e57 100644 --- a/NBus/Classes/Core/Bus+Error.swift +++ b/NBus/Classes/Core/Bus+Error.swift @@ -18,7 +18,7 @@ extension Bus { case unsupportedMessage - case invalidMessage + case invalidParameter case userCancelled diff --git a/NBus/Classes/Core/Bus+Helper.swift b/NBus/Classes/Core/Bus+Helper.swift index 6b4b25b..422a56a 100644 --- a/NBus/Classes/Core/Bus+Helper.swift +++ b/NBus/Classes/Core/Bus+Helper.swift @@ -8,10 +8,12 @@ import Foundation -extension Dictionary where Key == Bus.OauthInfoKey, Value == String? { +extension Dictionary: BusCompatible {} - func compactMapContent() -> [Bus.OauthInfoKey: String] { - compactMapValues { value -> String? in +extension BusWrapper where Base == [Bus.OauthInfoKey: String?] { + + public func compactMapContent() -> [Bus.OauthInfoKey: String] { + base.compactMapValues { value -> String? in guard let value = value, !value.isEmpty else { return nil } @@ -20,3 +22,77 @@ extension Dictionary where Key == Bus.OauthInfoKey, Value == String? { } } } + +extension String: BusCompatible {} + +extension BusWrapper where Base == String { + + var base64EncodedString: String? { + base.data(using: .utf8)?.base64EncodedString() + } +} + +extension NSObject: BusCompatible {} + +extension BusWrapper where Base: Bundle { + + private func value(forKeys keys: [String]) -> T? { + let infos = [ + base.localizedInfoDictionary ?? [:], + base.infoDictionary ?? [:], + ] + + for key in keys { + for info in infos { + if let value = info[key] as? T { + return value + } + } + } + + return nil + } + + public var identifier: String? { + value(forKeys: ["CFBundleIdentifier"]) + } + + public var displayName: String? { + value(forKeys: ["CFBundleDisplayName", "CFBundleName"]) + } +} + +extension BusWrapper where Base: UIDevice { + + private var systemInfo: utsname { + var systemInfo = utsname() + uname(&systemInfo) + return systemInfo + } + + private func toString(from mirror: Mirror) -> String { + let cString = mirror.children.compactMap { $1 as? Int8 } + return String(cString: cString) + } + + public var machine: String { + let mirror = Mirror(reflecting: systemInfo.machine) + return toString(from: mirror) + } +} + +extension BusWrapper where Base: UIPasteboard { + + public var oldText: String? { + guard + let typeListString = UIPasteboard.typeListString as? [String] + else { + assertionFailure() + return nil + } + + guard base.contains(pasteboardTypes: typeListString) else { return nil } + + return base.string + } +} diff --git a/NBus/Classes/Core/Bus+Message.swift b/NBus/Classes/Core/Bus+Message.swift index 50c3147..ccd3af5 100644 --- a/NBus/Classes/Core/Bus+Message.swift +++ b/NBus/Classes/Core/Bus+Message.swift @@ -104,6 +104,7 @@ public enum Messages { public static func file( data: Data, fileExtension: String, + fileName: String? = nil, title: String? = nil, description: String? = nil, thumbnail: Data? = nil @@ -111,6 +112,7 @@ public enum Messages { FileMessage( data: data, fileExtension: fileExtension, + fileName: fileName, title: title, description: description, thumbnail: thumbnail @@ -217,11 +219,17 @@ public struct FileMessage: MediaMessageType { public let fileExtension: String + public let fileName: String? + public let title: String? public let description: String? public let thumbnail: Data? + + public var fullName: String? { + fileName.map { "\($0).\(fileExtension)" } + } } public struct MiniProgramMessage: MessageType { diff --git a/NBus/Classes/Core/Bus+Wrapper.swift b/NBus/Classes/Core/Bus+Wrapper.swift new file mode 100644 index 0000000..6837278 --- /dev/null +++ b/NBus/Classes/Core/Bus+Wrapper.swift @@ -0,0 +1,64 @@ +// +// Bus+Wrapper.swift +// NBus +// +// Created by nuomi1 on 2021/1/4. +// Copyright © 2021 nuomi1. All rights reserved. +// + +import Foundation + +public struct BusWrapper { + + public let base: Base + + public init(_ base: Base) { + self.base = base + } +} + +public protocol BusCompatible { + + associatedtype BusBase + + var bus: BusWrapper { get set } +} + +extension BusCompatible { + + public var bus: BusWrapper { + get { BusWrapper(self) } + set {} + } +} + +@propertyWrapper +public struct BusUserDefaults { + + public let userDefaults: UserDefaults + + public let key: String + + public var wrappedValue: T? { + get { userDefaults.object(forKey: key) as? T } + set { userDefaults.set(newValue, forKey: key) } + } + + public init( + key: String, + userDefaults: UserDefaults = .standard + ) { + self.key = key + self.userDefaults = userDefaults + } +} + +extension BusUserDefaults { + + public init( + key: U, + userDefaults: UserDefaults = .standard + ) where U: RawRepresentable, U.RawValue == String { + self.init(key: key.rawValue, userDefaults: userDefaults) + } +} diff --git a/NBus/Classes/Core/Bus.swift b/NBus/Classes/Core/Bus.swift index a790570..9f6912e 100644 --- a/NBus/Classes/Core/Bus.swift +++ b/NBus/Classes/Core/Bus.swift @@ -139,3 +139,16 @@ extension Bus { return true } } + +extension Bus { + + enum OauthInfoKeys { + + enum QQ { + + static let accessToken = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.qq.accessToken") + + static let openID = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.qq.openID") + } + } +} diff --git a/NBus/Classes/Handler/QQHandler.swift b/NBus/Classes/Handler/QQHandler.swift new file mode 100644 index 0000000..953ba00 --- /dev/null +++ b/NBus/Classes/Handler/QQHandler.swift @@ -0,0 +1,631 @@ +// +// QQHandler.swift +// NBus +// +// Created by nuomi1 on 2021/1/4. +// Copyright © 2021 nuomi1. All rights reserved. +// + +import Foundation + +public class QQHandler { + + public let endpoints: [Endpoint] = [ + Endpoints.QQ.friend, + Endpoints.QQ.timeline, + ] + + public let platform: Platform = Platforms.qq + + public var isInstalled: Bool { + guard let url = URL(string: "mqq://") else { + assertionFailure() + return false + } + + return UIApplication.shared.canOpenURL(url) + } + + private var shareCompletionHandler: Bus.ShareCompletionHandler? + private var oauthCompletionHandler: Bus.OauthCompletionHandler? + + public let appID: String + public let universalLink: URL + + @BusUserDefaults(key: ShareOptionKeys.signToken) + private var signToken: String? + + public init(appID: String, universalLink: URL) { + self.appID = appID + self.universalLink = universalLink + } +} + +extension QQHandler: ShareHandlerType { + + public func share( + message: MessageType, + to endpoint: Endpoint, + options: [Bus.ShareOptionKey: Any], + completionHandler: @escaping Bus.ShareCompletionHandler + ) { + guard isInstalled else { + completionHandler(.failure(.missingApplication)) + return + } + + guard canShare(message: message.identifier, to: endpoint) else { + completionHandler(.failure(.unsupportedMessage)) + return + } + + guard + let identifierEncoded = identifier?.bus.base64EncodedString, + let cflag = cflag(endpoint, message.identifier), + let shareType = shareType(endpoint, message.identifier), + let displayNameEncoded = displayName?.bus.base64EncodedString + else { + assertionFailure() + completionHandler(.failure(.invalidParameter)) + return + } + + shareCompletionHandler = completionHandler + + var urlItems: [String: String] = [:] + var pasteBoardItems: [String: Any?] = [:] + + urlItems["appsign_txid"] = txID + urlItems["bundleid"] = identifierEncoded + urlItems["callback_name"] = txID + urlItems["callback_type"] = "scheme" + urlItems["cflag"] = cflag + urlItems["generalpastboard"] = "1" + urlItems["sdkv"] = sdkShortVersion + urlItems["shareType"] = shareType + urlItems["src_type"] = "app" + urlItems["thirdAppDisplayName"] = displayNameEncoded + urlItems["version"] = "1" + + if let signToken = signToken { + urlItems["appsign_token"] = signToken + } + + if let oldText = oldText { + pasteBoardItems["pasted_string"] = oldText + } + + if let message = message as? MediaMessageType { + if let title = message.title?.bus.base64EncodedString { + urlItems["title"] = title + } + + if let description = message.description?.bus.base64EncodedString { + urlItems["description"] = description + } + + if let thumbnail = message.thumbnail { + pasteBoardItems["previewimagedata"] = thumbnail + } + } + + switch message { + case let message as TextMessage: + guard + let text = message.text.bus.base64EncodedString + else { + completionHandler(.failure(.invalidParameter)) + return + } + + urlItems["file_type"] = "text" + + urlItems["file_data"] = text + + case let message as ImageMessage: + urlItems["file_type"] = "img" + + pasteBoardItems["file_data"] = message.data + + case let message as AudioMessage: + guard + let url = message.link.absoluteString.bus.base64EncodedString + else { + completionHandler(.failure(.invalidParameter)) + return + } + + urlItems["file_type"] = "audio" + + urlItems["url"] = url + + if let flashURL = message.dataLink?.absoluteString.bus.base64EncodedString { + urlItems["flashurl"] = flashURL + } + + case let message as VideoMessage: + guard + let url = message.link.absoluteString.bus.base64EncodedString + else { + completionHandler(.failure(.invalidParameter)) + return + } + + urlItems["file_type"] = "video" + + urlItems["url"] = url + + case let message as WebPageMessage: + guard + let url = message.link.absoluteString.bus.base64EncodedString + else { + completionHandler(.failure(.invalidParameter)) + return + } + + urlItems["file_type"] = "news" + + urlItems["url"] = url + + case let message as FileMessage: + urlItems["file_type"] = "localFile" + + if let fileName = message.fullName?.bus.base64EncodedString { + urlItems["fileName"] = fileName + } + + pasteBoardItems["file_data"] = message.data + + case let message as MiniProgramMessage: + guard + let path = message.path.bus.base64EncodedString, + let url = message.link.absoluteString.bus.base64EncodedString + else { + completionHandler(.failure(.invalidParameter)) + return + } + + urlItems["file_type"] = "news" + + urlItems["url"] = url + + if let thumbnail = message.thumbnail { + pasteBoardItems["previewimagedata"] = thumbnail + } + + urlItems["mini_appid"] = message.miniProgramID + urlItems["mini_path"] = path + urlItems["mini_weburl"] = url + urlItems["mini_type"] = miniProgramType(message.miniProgramType) + urlItems["mini_code64"] = "1" + + default: + assertionFailure() + completionHandler(.failure(.unsupportedMessage)) + return + } + + let pbItems = pasteBoardItems.compactMapValues { $0 } + + if !pbItems.isEmpty { + let pbData = NSKeyedArchiver.archivedData(withRootObject: pbItems) + + UIPasteboard.general.setData( + pbData, + forPasteboardType: "com.tencent.mqq.api.apiLargeData" + ) + } + + if pbItems.contains(where: { $0.key == "file_data" }) { + urlItems["objectlocation"] = "pasteboard" + } + + var components = URLComponents() + + components.scheme = "https" + components.host = "qm.qq.com" + components.path = "/opensdkul/mqqapi/share/to_fri" + + components.queryItems = urlItems.map { key, value in + URLQueryItem(name: key, value: value) + } + + guard let url = components.url else { + completionHandler(.failure(.invalidParameter)) + return + } + + guard UIApplication.shared.canOpenURL(url) else { + completionHandler(.failure(.unknown)) + return + } + + UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { result in + if !result { + completionHandler(.failure(.unknown)) + } + } + } + + private func canShare(message: Message, to endpoint: Endpoint) -> Bool { + switch endpoint { + case Endpoints.QQ.friend: + return true + case Endpoints.QQ.timeline: + return ![ + Messages.file, + Messages.miniProgram, + ].contains(message) + default: + assertionFailure() + return false + } + } + + private var appNumber: String { + appID.trimmingCharacters(in: .letters) + } + + private var txID: String { + "QQ\(String(format: "%08llX", (appNumber as NSString).longLongValue))" + } + + private var identifier: String? { + Bundle.main.bus.identifier + } + + private var displayName: String? { + Bundle.main.bus.displayName + } + + private var sdkShortVersion: String { + "3.5.1" + } + + private var sdkVersion: String { + "3.5.1_lite" + } + + private var oldText: String? { + UIPasteboard.general.bus.oldText + } + + private func cflag(_ endpoint: Endpoint, _ message: Message) -> String? { + var flags: [Int] = [] + + switch endpoint { + case Endpoints.QQ.friend: + flags.append(2) // qqapiCtrlFlagQZoneShareForbid + + if message == Messages.file { + flags.append(16) // qqapiCtrlFlagQQShareDataline + } + + if message == Messages.miniProgram { + flags.append(64) // kQQAPICtrlFlagQQShareEnableMiniProgram + } + case Endpoints.QQ.timeline: + break + default: + return nil + } + + let result = flags.reduce(0) { result, flag in result | flag } + return "\(result)" + } + + private func miniProgramType(_ miniProgramType: MiniProgramMessage.MiniProgramType) -> String { + let result: Int + + switch miniProgramType { + case .release: + result = 3 // online + case .test: + result = 1 // test + case .preview: + result = 4 // preview + } + + return "\(result)" + } + + private func shareType(_ endpoint: Endpoint, _ message: Message) -> String? { + switch endpoint { + case Endpoints.QQ.friend: + return "0" + case Endpoints.QQ.timeline: + return ![ + Messages.text, + Messages.image, + ].contains(message) + ? "1" : "0" + default: + return nil + } + } +} + +extension QQHandler: OauthHandlerType { + + public func oauth( + options: [Bus.OauthOptionKey: Any], + completionHandler: @escaping Bus.OauthCompletionHandler + ) { + guard isInstalled else { + completionHandler(.failure(.missingApplication)) + return + } + + guard + let displayName = displayName, + let identifier = identifier, + let identifierEncoded = identifier.bus.base64EncodedString + else { + assertionFailure() + completionHandler(.failure(.invalidParameter)) + return + } + + oauthCompletionHandler = completionHandler + + var urlItems: [String: String] = [:] + var pasteBoardItems: [String: Any?] = [:] + + pasteBoardItems["app_id"] = appNumber + pasteBoardItems["app_name"] = displayName + pasteBoardItems["bundleid"] = identifier + pasteBoardItems["client_id"] = appNumber + pasteBoardItems["refUniversallink"] = universalLink.absoluteString + pasteBoardItems["response_type"] = "token" + pasteBoardItems["scope"] = "get_user_info" + pasteBoardItems["sdkp"] = "i" + pasteBoardItems["sdkv"] = sdkVersion + pasteBoardItems["status_machine"] = statusMachine + pasteBoardItems["status_os"] = statusOS + pasteBoardItems["status_version"] = statusVersion + + let pbItems = pasteBoardItems.compactMapValues { $0 } + let pbData = NSKeyedArchiver.archivedData(withRootObject: pbItems) + + urlItems["appsign_txid"] = txID + urlItems["bundleid"] = identifierEncoded + urlItems["objectlocation"] = "url" + urlItems["pasteboard"] = pbData.base64EncodedString() + urlItems["sdkv"] = sdkShortVersion + + var components = URLComponents() + + components.scheme = "https" + components.host = "qm.qq.com" + components.path = "/opensdkul/mqqOpensdkSSoLogin/SSoLogin/\(appID)" + + components.queryItems = urlItems.map { key, value in + URLQueryItem(name: key, value: value) + } + + guard let url = components.url else { + completionHandler(.failure(.invalidParameter)) + return + } + + guard UIApplication.shared.canOpenURL(url) else { + completionHandler(.failure(.unknown)) + return + } + + UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { result in + if !result { + completionHandler(.failure(.unknown)) + } + } + } + + private var statusMachine: String { + UIDevice.current.bus.machine + } + + private var statusOS: String { + UIDevice.current.systemVersion + } + + private var statusVersion: String { + "\(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)" + } +} + +extension QQHandler: OpenURLHandlerType { + + public func openURL(_ url: URL) { + guard + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) + else { + assertionFailure() + return + } + + handleGeneral(with: components) + } +} + +extension QQHandler: OpenUserActivityHandlerType { + + public func openUserActivity(_ userActivity: NSUserActivity) { + guard + let url = userActivity.webpageURL, + let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let identifier = Bundle.main.bus.identifier + else { + assertionFailure() + return + } + + switch components.path { + case universalLink.appendingPathComponent("\(identifier)/mqqsignapp").path: + handleSignToken(with: components) + case universalLink.appendingPathComponent("\(identifier)").path: + handleActionInfo(with: components) + default: + assertionFailure() + } + } +} + +extension QQHandler { + + private func handleSignToken(with components: URLComponents) { + let decoder = JSONDecoder() + decoder.dataDecodingStrategy = .base64 + + guard + let item = components.queryItems?.first(where: { $0.name == "appsign_extrainfo" }), + let itemData = item.value.flatMap({ Data(base64Encoded: $0) }), + let infos = try? decoder.decode([String: String].self, from: itemData), + let appSignRedirect = infos["appsign_redirect"], + let appSignToken = infos["appsign_token"], + var components = URLComponents(string: appSignRedirect) + else { + assertionFailure() + return + } + + signToken = appSignToken + + var items: [String: String] = [:] + + items["openredirect"] = "1" + items["appsign_token"] = appSignToken + + components.scheme = "https" + components.host = "qm.qq.com" + components.path = "/opensdkul/mqqapi/share/to_fri" + + components.queryItems?.append(contentsOf: items.map { key, value in + URLQueryItem(name: key, value: value) + }) + + guard let url = components.url else { + shareCompletionHandler?(.failure(.invalidParameter)) + return + } + + guard UIApplication.shared.canOpenURL(url) else { + shareCompletionHandler?(.failure(.unknown)) + return + } + + UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { [weak self] result in + if !result { + self?.shareCompletionHandler?(.failure(.unknown)) + } + } + } + + private func handleActionInfo(with components: URLComponents) { + let decoder = JSONDecoder() + decoder.dataDecodingStrategy = .base64 + + guard + let item = components.queryItems?.first(where: { $0.name == "sdkactioninfo" }), + let itemData = item.value.flatMap({ Data(base64Encoded: $0) }), + let infos = try? decoder.decode([String: String].self, from: itemData) + else { + assertionFailure() + return + } + + var components = URLComponents() + + components.scheme = infos["sdk_action_sheme"] + components.host = infos["sdk_action_host"] + components.path = infos["sdk_action_path"] ?? "" + components.query = infos["sdk_action_query"] + + handleGeneral(with: components) + } + + private func handleGeneral(with components: URLComponents) { + switch components.host { + case "response_from_qq" where components.path == "": + handleShare(with: components) + case "qzapp" where components.path == "/mqzone/0": + handleOauth(with: components) + default: + assertionFailure() + } + } +} + +extension QQHandler { + + private func handleShare(with components: URLComponents) { + guard + let item = components.queryItems?.first(where: { $0.name == "error" }) + else { + assertionFailure() + return + } + + switch item.value { + case "0": + shareCompletionHandler?(.success(())) + case "-4": + shareCompletionHandler?(.failure(.userCancelled)) + default: + shareCompletionHandler?(.failure(.unknown)) + } + } + + private func handleOauth(with components: URLComponents) { + guard + let item = components.queryItems?.first(where: { $0.name == "pasteboard" }), + let itemData = item.value.flatMap({ Data(base64Encoded: $0) }), + let infos = NSKeyedUnarchiver.unarchiveObject(with: itemData) as? [String: Any] + else { + assertionFailure() + return + } + + let isUserCancelled = infos["user_cancelled"] as? String + + switch isUserCancelled { + case "YES": + oauthCompletionHandler?(.failure(.userCancelled)) + case "NO": + let accessToken = infos["access_token"] as? String + let openID = infos["openid"] as? String + + let parameters = [ + OauthInfoKeys.accessToken: accessToken, + OauthInfoKeys.openID: openID, + ] + .bus + .compactMapContent() + + if !parameters.isEmpty { + oauthCompletionHandler?(.success(parameters)) + } else { + oauthCompletionHandler?(.failure(.unknown)) + } + default: + assertionFailure() + } + } +} + +extension QQHandler { + + public enum ShareOptionKeys { + + public static let signToken = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.qqHandler.signToken") + } +} + +extension QQHandler { + + public enum OauthInfoKeys { + + public static let accessToken = Bus.OauthInfoKeys.QQ.accessToken + + public static let openID = Bus.OauthInfoKeys.QQ.openID + } +} diff --git a/NBus/Classes/Handler/QQSDKHandler.swift b/NBus/Classes/Handler/QQSDKHandler.swift index 71068f8..20d93b7 100644 --- a/NBus/Classes/Handler/QQSDKHandler.swift +++ b/NBus/Classes/Handler/QQSDKHandler.swift @@ -111,6 +111,8 @@ extension QQSDKHandler: ShareHandlerType { targetContentType: .audio ) + audioObject?.flashURL = message.dataLink + request = SendMessageToQQReq(content: audioObject) case let message as VideoMessage: @@ -125,7 +127,7 @@ extension QQSDKHandler: ShareHandlerType { request = SendMessageToQQReq(content: videoObject) case let message as WebPageMessage: - let webPageObject = QQApiURLObject( + let webPageObject = QQApiNewsObject( url: message.link, title: message.title, description: message.description, @@ -143,10 +145,12 @@ extension QQSDKHandler: ShareHandlerType { description: message.description ) + fileObject?.fileName = message.fullName + request = SendMessageToQQReq(content: fileObject) case let message as MiniProgramMessage: - let webPageObject = QQApiURLObject( + let webPageObject = QQApiNewsObject( url: message.link, title: message.link.absoluteString, description: "", @@ -188,7 +192,7 @@ extension QQSDKHandler: ShareHandlerType { case .EQQAPISENDSUCESS: break case .EQQAPIMESSAGECONTENTINVALID: - completionHandler(.failure(.invalidMessage)) + completionHandler(.failure(.invalidParameter)) default: completionHandler(.failure(.unknown)) } @@ -281,9 +285,9 @@ extension QQSDKHandler { public enum OauthInfoKeys { - public static let accessToken = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.qqSDKHandler.accessToken") + public static let accessToken = Bus.OauthInfoKeys.QQ.accessToken - public static let openID = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.qqSDKHandler.openID") + public static let openID = Bus.OauthInfoKeys.QQ.openID } } @@ -326,6 +330,7 @@ extension QQSDKHandler { OauthInfoKeys.accessToken: owner?.oauthCoordinator.accessToken, OauthInfoKeys.openID: owner?.oauthCoordinator.openId, ] + .bus .compactMapContent() if !parameters.isEmpty { diff --git a/NBus/Classes/Handler/SystemHandler.swift b/NBus/Classes/Handler/SystemHandler.swift index 498f4b1..eed3541 100644 --- a/NBus/Classes/Handler/SystemHandler.swift +++ b/NBus/Classes/Handler/SystemHandler.swift @@ -185,8 +185,12 @@ extension SystemHandler { public enum ShareOptionKeys { + // swiftlint:disable line_length + public static let presentingViewController = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.systemHandler.presentingViewController") + // swiftlint:enable line_length + public static let sourceView = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.systemHandler.sourceView") public static let sourceRect = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.systemHandler.sourceRect") @@ -199,8 +203,12 @@ extension SystemHandler { public static let identityToken = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.identityToken") + // swiftlint:disable line_length + public static let authorizationCode = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.authorizationCode") + // swiftlint:enable line_length + public static let user = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.user") public static let email = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.email") @@ -244,6 +252,7 @@ extension SystemHandler { OauthInfoKeys.givenName: credential.fullName?.givenName, OauthInfoKeys.familyName: credential.fullName?.familyName, ] + .bus .compactMapContent() if !parameters.isEmpty { diff --git a/NBus/Classes/Handler/WechatSDKHandler.swift b/NBus/Classes/Handler/WechatSDKHandler.swift index 0768618..2959d5a 100644 --- a/NBus/Classes/Handler/WechatSDKHandler.swift +++ b/NBus/Classes/Handler/WechatSDKHandler.swift @@ -147,7 +147,7 @@ extension WechatSDKHandler: ShareHandlerType { WXApi.send(request) { result in if !result { - completionHandler(.failure(.invalidMessage)) + completionHandler(.failure(.unknown)) } } } @@ -274,6 +274,7 @@ extension WechatSDKHandler { let parameters = [ OauthInfoKeys.code: code, ] + .bus .compactMapContent() if !parameters.isEmpty { diff --git a/NBus/Classes/Handler/WeiboSDKHandler.swift b/NBus/Classes/Handler/WeiboSDKHandler.swift index c70c0ce..b07b990 100644 --- a/NBus/Classes/Handler/WeiboSDKHandler.swift +++ b/NBus/Classes/Handler/WeiboSDKHandler.swift @@ -107,7 +107,7 @@ extension WeiboSDKHandler: ShareHandlerType { WeiboSDK.send(request) { result in if !result { - completionHandler(.failure(.invalidMessage)) + completionHandler(.failure(.unknown)) } } } @@ -205,6 +205,7 @@ extension WeiboSDKHandler { let parameters = [ OauthInfoKeys.accessToken: accessToken, ] + .bus .compactMapContent() if !parameters.isEmpty { diff --git a/README.md b/README.md index 626f63f..bba73dd 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,14 @@ nuomi1, nuomi1@qq.com ## Related articles -- [NBus 的由来](https://nuomi1.github.io/archives/2020/09/nbus-comes-from.html) -- [NBus 之 WechatSDKHandler](https://nuomi1.github.io/archives/2020/12/nbus-wechatsdkhandler.html) -- [NBus 之 QQSDKHandler](https://nuomi1.github.io/archives/2020/12/nbus-qqsdkhandler.html) -- [NBus 之 WeiboSDKHandler](https://nuomi1.github.io/archives/2020/12/nbus-weibosdkhandler.html) -- [NBus 之 SystemHandler](https://nuomi1.github.io/archives/2020/12/nbus-systemhandler.html) - -## Credits +- [NBus 的由来](https://blog.nuomi1.com/archives/2020/09/nbus-comes-from.html) +- [NBus 之 QQHandler](https://blog.nuomi1.com/archives/2021/01/nbus-qqhandler.html) +- [NBus 之 QQSDKHandler](https://blog.nuomi1.com/archives/2020/12/nbus-qqsdkhandler.html) +- [NBus 之 SystemHandler](https://blog.nuomi1.com/archives/2020/12/nbus-systemhandler.html) +- [NBus 之 WechatSDKHandler](https://blog.nuomi1.com/archives/2020/12/nbus-wechatsdkhandler.html) +- [NBus 之 WeiboSDKHandler](https://blog.nuomi1.com/archives/2020/12/nbus-weibosdkhandler.html) + +## Thanks - [MonkeyKing](https://github.com/nixzhu/MonkeyKing) - [LQThirdParty](https://github.com/LQi2009/LQThirdParty)