├── .gitignore ├── Icon.icns ├── LICENSE ├── Preview.png ├── README.md ├── WriteReceipt.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings └── WriteReceipt ├── Info.plist ├── WriteReceipt.m └── ZKSwizzle ├── ZKSwizzle.h └── ZKSwizzle.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shishkabibal/WriteReceipt/a703b391d4b3ef8932ac2c1cf5e2362fc70ab5b2/Icon.icns -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brian "Shishkabibal" 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shishkabibal/WriteReceipt/a703b391d4b3ef8932ac2c1cf5e2362fc70ab5b2/Preview.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WriteReceipt 2 | 3 | ![Preview](Preview.png) 4 | 5 | # Information: 6 | 7 | - WriteReceipt is a SIMBL plugin for Messages that leaves chats unread (and read receipts unsent) until you begin typing a reply (or press enter) 8 | - Tested on macOS 10.13.4 (Messages 11.0) 9 | - Author: [shishkabibal](https://github.com/shishkabibal) 10 | 11 | # Installation: 12 | 13 | 1. Download [mySIMBL](https://github.com/w0lfschild/mySIMBL/) 14 | 2. Download [WriteReceipt](https://github.com/shishkabibal/WriteReceipt/releases/latest) 15 | 3. Unzip both downloads 16 | 4. Open `WriteReceipt.bundle` with `mySIMBL.app` 17 | 5. Restart Messages to load WriteReceipt 18 | 19 | ### Appeal: 20 | Repurpose the code any way you like, but please don't just repackage it with your name. Remember to be nice to other people. If you make any changes and want to contribute, feel free to make a pull request. 21 | -------------------------------------------------------------------------------- /WriteReceipt.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D388E75D2093868300441C31 /* ZKSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = D388E7592093868300441C31 /* ZKSwizzle.m */; }; 11 | D388E75E2093868300441C31 /* WriteReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = D388E75B2093868300441C31 /* WriteReceipt.m */; }; 12 | D3E945B7209E016D00B67560 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = D3E945B6209E016D00B67560 /* Icon.icns */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | D388E74F2093866B00441C31 /* WriteReceipt.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WriteReceipt.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 17 | D388E7522093866B00441C31 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 18 | D388E7592093868300441C31 /* ZKSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZKSwizzle.m; sourceTree = ""; }; 19 | D388E75A2093868300441C31 /* ZKSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZKSwizzle.h; sourceTree = ""; }; 20 | D388E75B2093868300441C31 /* WriteReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WriteReceipt.m; sourceTree = ""; }; 21 | D3E945B6209E016D00B67560 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = SOURCE_ROOT; }; 22 | /* End PBXFileReference section */ 23 | 24 | /* Begin PBXFrameworksBuildPhase section */ 25 | D388E74C2093866B00441C31 /* Frameworks */ = { 26 | isa = PBXFrameworksBuildPhase; 27 | buildActionMask = 2147483647; 28 | files = ( 29 | ); 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXFrameworksBuildPhase section */ 33 | 34 | /* Begin PBXGroup section */ 35 | D388E7462093866B00441C31 = { 36 | isa = PBXGroup; 37 | children = ( 38 | D388E7512093866B00441C31 /* WriteReceipt */, 39 | D388E7502093866B00441C31 /* Products */, 40 | ); 41 | sourceTree = ""; 42 | }; 43 | D388E7502093866B00441C31 /* Products */ = { 44 | isa = PBXGroup; 45 | children = ( 46 | D388E74F2093866B00441C31 /* WriteReceipt.bundle */, 47 | ); 48 | name = Products; 49 | sourceTree = ""; 50 | }; 51 | D388E7512093866B00441C31 /* WriteReceipt */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | D388E7582093868300441C31 /* ZKSwizzle */, 55 | D388E75B2093868300441C31 /* WriteReceipt.m */, 56 | D3E945B6209E016D00B67560 /* Icon.icns */, 57 | D388E7522093866B00441C31 /* Info.plist */, 58 | ); 59 | path = WriteReceipt; 60 | sourceTree = ""; 61 | }; 62 | D388E7582093868300441C31 /* ZKSwizzle */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | D388E75A2093868300441C31 /* ZKSwizzle.h */, 66 | D388E7592093868300441C31 /* ZKSwizzle.m */, 67 | ); 68 | path = ZKSwizzle; 69 | sourceTree = ""; 70 | }; 71 | /* End PBXGroup section */ 72 | 73 | /* Begin PBXNativeTarget section */ 74 | D388E74E2093866B00441C31 /* WriteReceipt */ = { 75 | isa = PBXNativeTarget; 76 | buildConfigurationList = D388E7552093866B00441C31 /* Build configuration list for PBXNativeTarget "WriteReceipt" */; 77 | buildPhases = ( 78 | D388E74B2093866B00441C31 /* Sources */, 79 | D388E74C2093866B00441C31 /* Frameworks */, 80 | D388E74D2093866B00441C31 /* Resources */, 81 | ); 82 | buildRules = ( 83 | ); 84 | dependencies = ( 85 | ); 86 | name = WriteReceipt; 87 | productName = WriteReceipt; 88 | productReference = D388E74F2093866B00441C31 /* WriteReceipt.bundle */; 89 | productType = "com.apple.product-type.bundle"; 90 | }; 91 | /* End PBXNativeTarget section */ 92 | 93 | /* Begin PBXProject section */ 94 | D388E7472093866B00441C31 /* Project object */ = { 95 | isa = PBXProject; 96 | attributes = { 97 | LastUpgradeCheck = 0930; 98 | ORGANIZATIONNAME = Shishkabibal; 99 | TargetAttributes = { 100 | D388E74E2093866B00441C31 = { 101 | CreatedOnToolsVersion = 9.3; 102 | }; 103 | }; 104 | }; 105 | buildConfigurationList = D388E74A2093866B00441C31 /* Build configuration list for PBXProject "WriteReceipt" */; 106 | compatibilityVersion = "Xcode 9.3"; 107 | developmentRegion = en; 108 | hasScannedForEncodings = 0; 109 | knownRegions = ( 110 | en, 111 | ); 112 | mainGroup = D388E7462093866B00441C31; 113 | productRefGroup = D388E7502093866B00441C31 /* Products */; 114 | projectDirPath = ""; 115 | projectRoot = ""; 116 | targets = ( 117 | D388E74E2093866B00441C31 /* WriteReceipt */, 118 | ); 119 | }; 120 | /* End PBXProject section */ 121 | 122 | /* Begin PBXResourcesBuildPhase section */ 123 | D388E74D2093866B00441C31 /* Resources */ = { 124 | isa = PBXResourcesBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | D3E945B7209E016D00B67560 /* Icon.icns in Resources */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXResourcesBuildPhase section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | D388E74B2093866B00441C31 /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | D388E75E2093868300441C31 /* WriteReceipt.m in Sources */, 139 | D388E75D2093868300441C31 /* ZKSwizzle.m in Sources */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXSourcesBuildPhase section */ 144 | 145 | /* Begin XCBuildConfiguration section */ 146 | D388E7532093866B00441C31 /* Debug */ = { 147 | isa = XCBuildConfiguration; 148 | buildSettings = { 149 | ALWAYS_SEARCH_USER_PATHS = NO; 150 | CLANG_ANALYZER_NONNULL = YES; 151 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 152 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 153 | CLANG_CXX_LIBRARY = "libc++"; 154 | CLANG_ENABLE_MODULES = YES; 155 | CLANG_ENABLE_OBJC_ARC = YES; 156 | CLANG_ENABLE_OBJC_WEAK = YES; 157 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 158 | CLANG_WARN_BOOL_CONVERSION = YES; 159 | CLANG_WARN_COMMA = YES; 160 | CLANG_WARN_CONSTANT_CONVERSION = YES; 161 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 162 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 163 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 164 | CLANG_WARN_EMPTY_BODY = YES; 165 | CLANG_WARN_ENUM_CONVERSION = YES; 166 | CLANG_WARN_INFINITE_RECURSION = YES; 167 | CLANG_WARN_INT_CONVERSION = YES; 168 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 169 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 170 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 171 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 172 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 173 | CLANG_WARN_STRICT_PROTOTYPES = YES; 174 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 175 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 176 | CLANG_WARN_UNREACHABLE_CODE = YES; 177 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 178 | CODE_SIGN_IDENTITY = "-"; 179 | COPY_PHASE_STRIP = NO; 180 | DEBUG_INFORMATION_FORMAT = dwarf; 181 | DEPLOYMENT_POSTPROCESSING = NO; 182 | ENABLE_STRICT_OBJC_MSGSEND = YES; 183 | ENABLE_TESTABILITY = YES; 184 | GCC_C_LANGUAGE_STANDARD = gnu11; 185 | GCC_DYNAMIC_NO_PIC = NO; 186 | GCC_NO_COMMON_BLOCKS = YES; 187 | GCC_OPTIMIZATION_LEVEL = 0; 188 | GCC_PREPROCESSOR_DEFINITIONS = ( 189 | "DEBUG=1", 190 | "$(inherited)", 191 | ); 192 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 193 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 194 | GCC_WARN_UNDECLARED_SELECTOR = YES; 195 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 196 | GCC_WARN_UNUSED_FUNCTION = YES; 197 | GCC_WARN_UNUSED_VARIABLE = YES; 198 | MACOSX_DEPLOYMENT_TARGET = 10.13; 199 | MTL_ENABLE_DEBUG_INFO = YES; 200 | ONLY_ACTIVE_ARCH = YES; 201 | SDKROOT = macosx; 202 | }; 203 | name = Debug; 204 | }; 205 | D388E7542093866B00441C31 /* Release */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 212 | CLANG_CXX_LIBRARY = "libc++"; 213 | CLANG_ENABLE_MODULES = YES; 214 | CLANG_ENABLE_OBJC_ARC = YES; 215 | CLANG_ENABLE_OBJC_WEAK = YES; 216 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 217 | CLANG_WARN_BOOL_CONVERSION = YES; 218 | CLANG_WARN_COMMA = YES; 219 | CLANG_WARN_CONSTANT_CONVERSION = YES; 220 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 222 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 223 | CLANG_WARN_EMPTY_BODY = YES; 224 | CLANG_WARN_ENUM_CONVERSION = YES; 225 | CLANG_WARN_INFINITE_RECURSION = YES; 226 | CLANG_WARN_INT_CONVERSION = YES; 227 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | CODE_SIGN_IDENTITY = "-"; 238 | COPY_PHASE_STRIP = NO; 239 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 240 | DEPLOYMENT_POSTPROCESSING = NO; 241 | ENABLE_NS_ASSERTIONS = NO; 242 | ENABLE_STRICT_OBJC_MSGSEND = YES; 243 | GCC_C_LANGUAGE_STANDARD = gnu11; 244 | GCC_NO_COMMON_BLOCKS = YES; 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 247 | GCC_WARN_UNDECLARED_SELECTOR = YES; 248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 249 | GCC_WARN_UNUSED_FUNCTION = YES; 250 | GCC_WARN_UNUSED_VARIABLE = YES; 251 | MACOSX_DEPLOYMENT_TARGET = 10.13; 252 | MTL_ENABLE_DEBUG_INFO = NO; 253 | SDKROOT = macosx; 254 | }; 255 | name = Release; 256 | }; 257 | D388E7562093866B00441C31 /* Debug */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | CODE_SIGN_STYLE = Automatic; 261 | COMBINE_HIDPI_IMAGES = YES; 262 | DEPLOYMENT_POSTPROCESSING = NO; 263 | INFOPLIST_FILE = WriteReceipt/Info.plist; 264 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Application Support/SIMBL/Plugins"; 265 | PRODUCT_BUNDLE_IDENTIFIER = com.shishkabibal.WriteReceipt; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | SKIP_INSTALL = YES; 268 | WRAPPER_EXTENSION = bundle; 269 | }; 270 | name = Debug; 271 | }; 272 | D388E7572093866B00441C31 /* Release */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | CODE_SIGN_STYLE = Automatic; 276 | COMBINE_HIDPI_IMAGES = YES; 277 | DEPLOYMENT_POSTPROCESSING = NO; 278 | INFOPLIST_FILE = WriteReceipt/Info.plist; 279 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Application Support/SIMBL/Plugins"; 280 | PRODUCT_BUNDLE_IDENTIFIER = com.shishkabibal.WriteReceipt; 281 | PRODUCT_NAME = "$(TARGET_NAME)"; 282 | SKIP_INSTALL = YES; 283 | WRAPPER_EXTENSION = bundle; 284 | }; 285 | name = Release; 286 | }; 287 | /* End XCBuildConfiguration section */ 288 | 289 | /* Begin XCConfigurationList section */ 290 | D388E74A2093866B00441C31 /* Build configuration list for PBXProject "WriteReceipt" */ = { 291 | isa = XCConfigurationList; 292 | buildConfigurations = ( 293 | D388E7532093866B00441C31 /* Debug */, 294 | D388E7542093866B00441C31 /* Release */, 295 | ); 296 | defaultConfigurationIsVisible = 0; 297 | defaultConfigurationName = Release; 298 | }; 299 | D388E7552093866B00441C31 /* Build configuration list for PBXNativeTarget "WriteReceipt" */ = { 300 | isa = XCConfigurationList; 301 | buildConfigurations = ( 302 | D388E7562093866B00441C31 /* Debug */, 303 | D388E7572093866B00441C31 /* Release */, 304 | ); 305 | defaultConfigurationIsVisible = 0; 306 | defaultConfigurationName = Release; 307 | }; 308 | /* End XCConfigurationList section */ 309 | }; 310 | rootObject = D388E7472093866B00441C31 /* Project object */; 311 | } 312 | -------------------------------------------------------------------------------- /WriteReceipt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WriteReceipt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WriteReceipt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /WriteReceipt/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | Icon 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.1 21 | CFBundleSignature 22 | ???? 23 | NSHumanReadableCopyright 24 | Copyright © 2018 Brian "Shishkabibal". All rights reserved. 25 | NSPrincipalClass 26 | WriteReceipt 27 | SIMBLTargetApplications 28 | 29 | 30 | BundleIdentifier 31 | com.apple.iChat 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /WriteReceipt/WriteReceipt.m: -------------------------------------------------------------------------------- 1 | // 2 | // WriteReceipt.m 3 | // WriteReceipt 4 | // 5 | // Created by Brian "Shishkabibal" on 4/26/18. 6 | // Copyright (c) 2018 Brian "Shishkabibal". All rights reserved. 7 | // 8 | 9 | // Imports 10 | 11 | @import AppKit; 12 | #import "ZKSwizzle.h" 13 | 14 | // Interfaces 15 | 16 | @interface WriteReceipt : NSObject 17 | @end 18 | 19 | @interface UnifiedChatListViewController : NSViewController 20 | - (void)markAsRead:(id)arg1; 21 | @end 22 | 23 | @interface NSUserNotificationCenter (Private) 24 | - (void)_removeDisplayedNotification:(id)arg1; 25 | @end 26 | 27 | // Variables 28 | 29 | WriteReceipt* plugin; 30 | UnifiedChatListViewController* unifiedChatListViewController; 31 | BOOL keyPressed; 32 | 33 | // WriteReceipt 34 | 35 | @implementation WriteReceipt 36 | 37 | + (WriteReceipt*) sharedInstance { 38 | static WriteReceipt* plugin = nil; 39 | 40 | if (plugin == nil) 41 | plugin = [[WriteReceipt alloc] init]; 42 | 43 | return plugin; 44 | } 45 | 46 | +(void)load { 47 | NSLog(@"WriteReceipt: Plugin Loaded"); 48 | 49 | plugin = [WriteReceipt sharedInstance]; 50 | } 51 | 52 | @end 53 | 54 | // ZKSwizzleInterfaces 55 | 56 | ZKSwizzleInterface(BSUnifiedChatWindowController, UnifiedChatWindowController, NSWindowController) 57 | @implementation BSUnifiedChatWindowController 58 | 59 | // Required because `[ChatController markAllMessagesAsRead]` used to handle this 60 | - (void)userNotificationCenter:(id)arg1 didActivateNotification:(id)arg2 { 61 | [arg1 _removeDisplayedNotification:arg2]; 62 | ZKOrig(void, arg1, arg2); 63 | } 64 | 65 | @end 66 | 67 | ZKSwizzleInterface(BSUnifiedChatListViewController, UnifiedChatListViewController, NSViewController) 68 | @implementation BSUnifiedChatListViewController 69 | 70 | // Reqired to call `[UnifiedChatListViewController markAsRead:arg1]` from `SOInputLine` 71 | - (void)viewDidAppear { 72 | NSLog(@"WriteReceipt: Hooked UnifiedChatListViewController"); 73 | 74 | unifiedChatListViewController = (UnifiedChatListViewController*)self; 75 | 76 | ZKOrig(void); 77 | } 78 | 79 | @end 80 | 81 | ZKSwizzleInterface(BSChatController, ChatController, NSObject) 82 | @implementation BSChatController 83 | 84 | // Block `[ChatController markAllMessagesAsRead]` unless `keyPressed` 85 | - (void)markAllMessagesAsRead { 86 | if (keyPressed) { 87 | NSLog(@"WriteReceipt: Modified markAllMessagesAsRead"); 88 | 89 | keyPressed = NO; 90 | 91 | ZKOrig(void); 92 | } else { 93 | NSLog(@"WriteReceipt: Blocked markAllMessagesAsRead"); 94 | } 95 | } 96 | 97 | @end 98 | 99 | ZKSwizzleInterface(BSSOInputLine, SOInputLine, NSTextView) 100 | @implementation BSSOInputLine 101 | 102 | // Initiate `[unifiedChatListViewController markAsRead:arg1]` with `keyPressed` 103 | - (void)keyDown:(id)arg1 { 104 | NSLog(@"WriteReceipt: Modified keyDown"); 105 | 106 | keyPressed = YES; 107 | [unifiedChatListViewController markAsRead:arg1]; 108 | 109 | ZKOrig(void, arg1); 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /WriteReceipt/ZKSwizzle/ZKSwizzle.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZKSwizzle.h 3 | // ZKSwizzle 4 | // 5 | // Created by Alexander S Zielenski on 7/24/14. 6 | // Copyright (c) 2014 Alexander S Zielenski. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | // This is a class for streamlining swizzling. Simply create a new class of any name you want and 14 | // Example: 15 | /* 16 | @interface ZKHookClass : NSObject 17 | - (NSString *)description; // hooks -description on NSObject 18 | - (void)addedMethod; // all subclasses of NSObject now respond to -addedMethod 19 | @end 20 | 21 | @implementation ZKHookClass 22 | ... 23 | @end 24 | 25 | [ZKSwizzle swizzleClass:ZKClass(ZKHookClass) forClass:ZKClass(destination)]; 26 | */ 27 | 28 | #ifndef ZKSWIZZLE_DEFS 29 | #define ZKSWIZZLE_DEFS 30 | 31 | // CRAZY MACROS FOR DYNAMIC PROTOTYPE CREATION 32 | #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5 ,4 ,3 ,2, 1, 0) 33 | #define VA_NUM_ARGS_IMPL(_0, _1,_2,_3,_4,_5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 ,N,...) N 34 | 35 | #define WRAP0() 36 | #define WRAP1(VARIABLE) , typeof ( VARIABLE ) 37 | #define WRAP2(VARIABLE, ...) WRAP1(VARIABLE) WRAP1(__VA_ARGS__) 38 | #define WRAP3(VARIABLE, ...) WRAP1(VARIABLE) WRAP2(__VA_ARGS__) 39 | #define WRAP4(VARIABLE, ...) WRAP1(VARIABLE) WRAP3(__VA_ARGS__) 40 | #define WRAP5(VARIABLE, ...) WRAP1(VARIABLE) WRAP4(__VA_ARGS__) 41 | #define WRAP6(VARIABLE, ...) WRAP1(VARIABLE) WRAP5(__VA_ARGS__) 42 | #define WRAP7(VARIABLE, ...) WRAP1(VARIABLE) WRAP6(__VA_ARGS__) 43 | #define WRAP8(VARIABLE, ...) WRAP1(VARIABLE) WRAP7(__VA_ARGS__) 44 | #define WRAP9(VARIABLE, ...) WRAP1(VARIABLE) WRAP8(__VA_ARGS__) 45 | #define WRAP10(VARIABLE, ...) WRAP1(VARIABLE) WRAP9(__VA_ARGS__) 46 | #define WRAP11(VARIABLE, ...) WRAP1(VARIABLE) WRAP10(__VA_ARGS__) 47 | #define WRAP12(VARIABLE, ...) WRAP1(VARIABLE) WRAP11(__VA_ARGS__) 48 | #define WRAP13(VARIABLE, ...) WRAP1(VARIABLE) WRAP12(__VA_ARGS__) 49 | #define WRAP14(VARIABLE, ...) WRAP1(VARIABLE) WRAP13(__VA_ARGS__) 50 | #define WRAP15(VARIABLE, ...) WRAP1(VARIABLE) WRAP14(__VA_ARGS__) 51 | #define WRAP16(VARIABLE, ...) WRAP1(VARIABLE) WRAP15(__VA_ARGS__) 52 | #define WRAP17(VARIABLE, ...) WRAP1(VARIABLE) WRAP16(__VA_ARGS__) 53 | #define WRAP18(VARIABLE, ...) WRAP1(VARIABLE) WRAP17(__VA_ARGS__) 54 | #define WRAP19(VARIABLE, ...) WRAP1(VARIABLE) WRAP18(__VA_ARGS__) 55 | #define WRAP20(VARIABLE, ...) WRAP1(VARIABLE) WRAP19(__VA_ARGS__) 56 | 57 | #define CAT(A, B) A ## B 58 | #define INVOKE(MACRO, NUMBER, ...) CAT(MACRO, NUMBER)(__VA_ARGS__) 59 | #define WRAP_LIST(...) INVOKE(WRAP, VA_NUM_ARGS(__VA_ARGS__), __VA_ARGS__) 60 | 61 | // Gets the a class with the name CLASS 62 | #define ZKClass(CLASS) objc_getClass(#CLASS) 63 | 64 | // returns the value of an instance variable. 65 | #if !__has_feature(objc_arc) 66 | #define ZKHookIvar(OBJECT, TYPE, NAME) (*(TYPE *)ZKIvarPointer(OBJECT, NAME)) 67 | #else 68 | #define ZKHookIvar(OBJECT, TYPE, NAME) \ 69 | _Pragma("clang diagnostic push") \ 70 | _Pragma("clang diagnostic ignored \"-Wignored-attributes\"") \ 71 | (*(__unsafe_unretained TYPE *)ZKIvarPointer(OBJECT, NAME)) \ 72 | _Pragma("clang diagnostic pop") 73 | #endif 74 | // returns the original implementation of the swizzled function or null or not found 75 | #define ZKOrig(TYPE, ...) ((TYPE (*)(id, SEL WRAP_LIST(__VA_ARGS__)))(ZKOriginalImplementation(self, _cmd, __PRETTY_FUNCTION__)))(self, _cmd, ##__VA_ARGS__) 76 | 77 | // returns the original implementation of the superclass of the object swizzled 78 | #define ZKSuper(TYPE, ...) ((TYPE (*)(id, SEL WRAP_LIST(__VA_ARGS__)))(ZKSuperImplementation(self, _cmd, __PRETTY_FUNCTION__)))(self, _cmd, ##__VA_ARGS__) 79 | 80 | #define _ZKSwizzleInterfaceConditionally(CLASS_NAME, TARGET_CLASS, SUPERCLASS, GROUP, IMMEDIATELY) \ 81 | @interface _$ ## CLASS_NAME : SUPERCLASS @end \ 82 | @implementation _$ ## CLASS_NAME \ 83 | + (void)initialize {} \ 84 | @end \ 85 | @interface CLASS_NAME : _$ ## CLASS_NAME @end \ 86 | @implementation CLASS_NAME (ZKSWIZZLE) \ 87 | + (void)load { \ 88 | _$ZKRegisterInterface(self, #GROUP);\ 89 | if (IMMEDIATELY) { \ 90 | [self _ZK_unconditionallySwizzle]; \ 91 | } \ 92 | } \ 93 | + (void)_ZK_unconditionallySwizzle { \ 94 | ZKSwizzle(CLASS_NAME, TARGET_CLASS); \ 95 | } \ 96 | @end 97 | 98 | // Bootstraps your swizzling class so that it requires no setup 99 | // outside of this macro call 100 | // If you override +load you must call ZKSwizzle(CLASS_NAME, TARGET_CLASS) 101 | // yourself, otherwise the swizzling would not take place 102 | #define ZKSwizzleInterface(CLASS_NAME, TARGET_CLASS, SUPERCLASS) \ 103 | _ZKSwizzleInterfaceConditionally(CLASS_NAME, TARGET_CLASS, SUPERCLASS, ZK_UNGROUPED, YES) 104 | 105 | // Same as ZKSwizzleInterface, except 106 | #define ZKSwizzleInterfaceGroup(CLASS_NAME, TARGET_CLASS, SUPER_CLASS, GROUP) \ 107 | _ZKSwizzleInterfaceConditionally(CLASS_NAME, TARGET_CLASS, SUPER_CLASS, GROUP, NO) 108 | 109 | __BEGIN_DECLS 110 | 111 | // Make sure to cast this before you use it 112 | typedef id (*ZKIMP)(id, SEL, ...); 113 | 114 | // returns a pointer to the instance variable "name" on the object 115 | void *ZKIvarPointer(id self, const char *name); 116 | // returns the original implementation of a method with selector "sel" of an object hooked by the methods below 117 | ZKIMP ZKOriginalImplementation(id self, SEL sel, const char *info); 118 | // returns the implementation of a method with selector "sel" of the superclass of object 119 | ZKIMP ZKSuperImplementation(id object, SEL sel, const char *info); 120 | 121 | // hooks all the implemented methods of source with destination 122 | // adds any methods that arent implemented on destination to destination that are implemented in source 123 | #define ZKSwizzle(src, dst) _ZKSwizzle(ZKClass(src), ZKClass(dst)) 124 | BOOL _ZKSwizzle(Class src, Class dest); 125 | 126 | #define ZKSwizzleGroup(NAME) _ZKSwizzleGroup(#NAME) 127 | void _$ZKRegisterInterface(Class cls, const char *groupName); 128 | BOOL _ZKSwizzleGroup(const char *groupName); 129 | 130 | // Calls above method with the superclass of source for desination 131 | #define ZKSwizzleClass(src) _ZKSwizzleClass(ZKClass(src)) 132 | BOOL _ZKSwizzleClass(Class cls); 133 | 134 | __END_DECLS 135 | #endif 136 | 137 | -------------------------------------------------------------------------------- /WriteReceipt/ZKSwizzle/ZKSwizzle.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZKSwizzle.m 3 | // ZKSwizzle 4 | // 5 | // Created by Alexander S Zielenski on 7/24/14. 6 | // Copyright (c) 2014 Alexander S Zielenski. All rights reserved. 7 | // 8 | 9 | #import "ZKSwizzle.h" 10 | static NSMutableDictionary *classTable; 11 | 12 | @interface NSObject (ZKSwizzle) 13 | + (void)_ZK_unconditionallySwizzle; 14 | @end 15 | 16 | void *ZKIvarPointer(id self, const char *name) { 17 | Ivar ivar = class_getInstanceVariable(object_getClass(self), name); 18 | return ivar == NULL ? NULL : (__bridge void *)self + ivar_getOffset(ivar); 19 | } 20 | 21 | static SEL destinationSelectorForSelector(SEL cmd, Class dst) { 22 | return NSSelectorFromString([@"_ZK_old_" stringByAppendingFormat:@"%s_%@", class_getName(dst), NSStringFromSelector(cmd)]); 23 | } 24 | 25 | static Class classFromInfo(const char *info) { 26 | NSUInteger bracket_index = -1; 27 | for (NSUInteger i = 0; i < strlen(info); i++) { 28 | if (info[i] == '[') { 29 | bracket_index = i; 30 | } 31 | } 32 | bracket_index++; 33 | 34 | if (bracket_index == -1) { 35 | [NSException raise:@"Failed to parse info" format:@"Couldn't find swizzle class for info: %s", info]; 36 | return NULL; 37 | } 38 | 39 | char after_bracket[255]; 40 | memcpy(after_bracket, &info[bracket_index], strlen(info) - bracket_index - 1); 41 | 42 | for (NSUInteger i = 0; i < strlen(info); i++) { 43 | if (after_bracket[i] == ' ') { 44 | after_bracket[i] = '\0'; 45 | } 46 | } 47 | 48 | return objc_getClass(after_bracket); 49 | } 50 | 51 | // takes __PRETTY_FUNCTION__ for info which gives the name of the swizzle source class 52 | /* 53 | 54 | We add the original implementation onto the swizzle class 55 | On ZKOrig, we use __PRETTY_FUNCTION__ to get the name of the swizzle class 56 | Then we get the implementation of that selector on the swizzle class 57 | Then we call it directly, passing in the correct selector and self 58 | 59 | */ 60 | ZKIMP ZKOriginalImplementation(id self, SEL sel, const char *info) { 61 | if (sel == NULL || self == NULL || info == NULL) { 62 | [NSException raise:@"Invalid Arguments" format:@"One of self: %@, self: %@, or info: %s is NULL", self, NSStringFromSelector(sel), info]; 63 | return NULL; 64 | } 65 | 66 | Class cls = classFromInfo(info); 67 | Class dest = object_getClass(self); 68 | 69 | if (cls == NULL || dest == NULL) { 70 | [NSException raise:@"Failed obtain class pair" format:@"src: %@ | dst: %@ | sel: %@", NSStringFromClass(cls), NSStringFromClass(dest), NSStringFromSelector(sel)]; 71 | return NULL; 72 | } 73 | 74 | SEL destSel = destinationSelectorForSelector(sel, cls); 75 | 76 | Method method = class_getInstanceMethod(dest, destSel); 77 | 78 | if (method == NULL) { 79 | [NSException raise:@"Failed to retrieve method" format:@"Got null for the source class %@ with selector %@ (%@)", NSStringFromClass(cls), NSStringFromSelector(sel), NSStringFromSelector(destSel)]; 80 | return NULL; 81 | } 82 | 83 | ZKIMP implementation = (ZKIMP)method_getImplementation(method); 84 | if (implementation == NULL) { 85 | [NSException raise:@"Failed to get implementation" format:@"The objective-c runtime could not get the implementation for %@ on the class %@. There is no fix for this", NSStringFromClass(cls), NSStringFromSelector(sel)]; 86 | } 87 | 88 | return implementation; 89 | } 90 | 91 | ZKIMP ZKSuperImplementation(id object, SEL sel, const char *info) { 92 | if (sel == NULL || object == NULL) { 93 | [NSException raise:@"Invalid Arguments" format:@"One of self: %@, self: %@ is NULL", object, NSStringFromSelector(sel)]; 94 | return NULL; 95 | } 96 | 97 | Class cls = object_getClass(object); 98 | if (cls == NULL) { 99 | [NSException raise:@"Invalid Argument" format:@"Could not obtain class for the passed object"]; 100 | return NULL; 101 | } 102 | 103 | // Two scenarios: 104 | // 1.) The superclass was not swizzled, no problem 105 | // 2.) The superclass was swizzled, problem 106 | 107 | // We want to return the swizzled class's superclass implementation 108 | // If this is a subclass of such a class, we want two behaviors: 109 | // a.) If this imp was also swizzled, no problem, return the superclass's swizzled imp 110 | // b.) This imp was not swizzled, return the class that was originally swizzled's superclass's imp 111 | Class sourceClass = classFromInfo(info); 112 | if (sourceClass != NULL) { 113 | BOOL isClassMethod = class_isMetaClass(cls); 114 | // This was called from a swizzled method, get the class it was swizzled with 115 | NSString *className = classTable[NSStringFromClass(sourceClass)]; 116 | if (className != NULL) { 117 | cls = NSClassFromString(className); 118 | // make sure we get a class method if we asked for one 119 | if (isClassMethod) { 120 | cls = object_getClass(cls); 121 | } 122 | } 123 | } 124 | 125 | cls = class_getSuperclass(cls); 126 | 127 | // This is a root class, it has no super class 128 | if (cls == NULL) { 129 | [NSException raise:@"Invalid Argument" format:@"Could not obtain superclass for the passed object"]; 130 | return NULL; 131 | } 132 | 133 | Method method = class_getInstanceMethod(cls, sel); 134 | if (method == NULL) { 135 | [NSException raise:@"Failed to retrieve method" format:@"We could not find the super implementation for the class %@ and selector %@, are you sure it exists?", NSStringFromClass(cls), NSStringFromSelector(sel)]; 136 | return NULL; 137 | } 138 | 139 | ZKIMP implementation = (ZKIMP)method_getImplementation(method); 140 | if (implementation == NULL) { 141 | [NSException raise:@"Failed to get implementation" format:@"The objective-c runtime could not get the implementation for %@ on the class %@. There is no fix for this", NSStringFromClass(cls), NSStringFromSelector(sel)]; 142 | } 143 | 144 | return implementation; 145 | } 146 | 147 | static BOOL enumerateMethods(Class, Class); 148 | BOOL _ZKSwizzle(Class src, Class dest) { 149 | if (dest == NULL) 150 | return NO; 151 | 152 | NSString *destName = NSStringFromClass(dest); 153 | if (!destName) { 154 | return NO; 155 | } 156 | 157 | if (!classTable) { 158 | classTable = [[NSMutableDictionary alloc] init]; 159 | } 160 | 161 | if ([classTable objectForKey:NSStringFromClass(src)]) { 162 | [NSException raise:@"Invalid Argument" 163 | format:@"This source class (%@) was already swizzled with another, (%@)", NSStringFromClass(src), classTable[NSStringFromClass(src)]]; 164 | return NO; 165 | } 166 | 167 | BOOL success = enumerateMethods(dest, src); 168 | // The above method only gets instance methods. Do the same method for the metaclass of the class 169 | success &= enumerateMethods(object_getClass(dest), object_getClass(src)); 170 | 171 | [classTable setObject:destName forKey:NSStringFromClass(src)]; 172 | return success; 173 | } 174 | 175 | BOOL _ZKSwizzleClass(Class cls) { 176 | return _ZKSwizzle(cls, [cls superclass]); 177 | } 178 | 179 | static BOOL enumerateMethods(Class destination, Class source) { 180 | #if OBJC_API_VERSION < 2 181 | [NSException raise:@"Unsupported feature" format:@"ZKSwizzle is only available in objc 2.0"]; 182 | return NO; 183 | 184 | #else 185 | 186 | unsigned int methodCount; 187 | Method *methodList = class_copyMethodList(source, &methodCount); 188 | BOOL success = YES; 189 | for (int i = 0; i < methodCount; i++) { 190 | Method method = methodList[i]; 191 | SEL selector = method_getName(method); 192 | NSString *methodName = NSStringFromSelector(selector); 193 | 194 | // Don't do anything with the unconditional swizzle 195 | if (sel_isEqual(selector, @selector(_ZK_unconditionallySwizzle))) { 196 | continue; 197 | } 198 | 199 | // We only swizzle methods that are implemented 200 | if (class_respondsToSelector(destination, selector)) { 201 | Method originalMethod = class_getInstanceMethod(destination, selector); 202 | 203 | const char *originalType = method_getTypeEncoding(originalMethod); 204 | const char *newType = method_getTypeEncoding(method); 205 | if (strcmp(originalType, newType) != 0) { 206 | NSLog(@"ZKSwizzle: incompatible type encoding for %@. (expected %s, got %s)", methodName, originalType, newType); 207 | // Incompatible type encoding 208 | success = NO; 209 | continue; 210 | } 211 | 212 | // We are re-adding the destination selector because it could be on a superclass and not on the class itself. This method could fail 213 | class_addMethod(destination, selector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 214 | 215 | SEL destSel = destinationSelectorForSelector(selector, source); 216 | if (!class_addMethod(destination, destSel, method_getImplementation(method), method_getTypeEncoding(originalMethod))) { 217 | NSLog(@"ZKSwizzle: failed to add method %@ onto class %@ with selector %@", NSStringFromSelector(selector), NSStringFromClass(source), NSStringFromSelector(destSel)); 218 | success = NO; 219 | continue; 220 | } 221 | 222 | method_exchangeImplementations(class_getInstanceMethod(destination, selector), class_getInstanceMethod(destination, destSel)); 223 | } else { 224 | // Add any extra methods to the class but don't swizzle them 225 | success &= class_addMethod(destination, selector, method_getImplementation(method), method_getTypeEncoding(method)); 226 | } 227 | } 228 | 229 | unsigned int propertyCount; 230 | objc_property_t *propertyList = class_copyPropertyList(source, &propertyCount); 231 | for (int i = 0; i < propertyCount; i++) { 232 | objc_property_t property = propertyList[i]; 233 | const char *name = property_getName(property); 234 | unsigned int attributeCount; 235 | objc_property_attribute_t *attributes = property_copyAttributeList(property, &attributeCount); 236 | 237 | if (class_getProperty(destination, name) == NULL) { 238 | class_addProperty(destination, name, attributes, attributeCount); 239 | } else { 240 | class_replaceProperty(destination, name, attributes, attributeCount); 241 | } 242 | 243 | free(attributes); 244 | } 245 | 246 | free(propertyList); 247 | free(methodList); 248 | return success; 249 | #endif 250 | } 251 | 252 | // Options were to use a group class and traverse its subclasses 253 | // or to create a groups dictionary 254 | // This works because +load on NSObject is called before attribute((constructor)) 255 | static NSMutableDictionary *groups = nil; 256 | void _$ZKRegisterInterface(Class cls, const char *groupName) { 257 | if (!groups) 258 | groups = [NSMutableDictionary dictionary]; 259 | 260 | NSString *groupString = @(groupName); 261 | NSMutableArray *groupList = groups[groupString]; 262 | if (!groupList) { 263 | groupList = [NSMutableArray array]; 264 | groups[groupString] = groupList; 265 | } 266 | 267 | [groupList addObject:NSStringFromClass(cls)]; 268 | } 269 | 270 | BOOL _ZKSwizzleGroup(const char *groupName) { 271 | NSArray *groupList = groups[@(groupName)]; 272 | if (!groupList) { 273 | [NSException raise:@"Invalid Argument" format:@"ZKSwizzle: There is no group by the name of %s", groupName]; 274 | return NO; 275 | } 276 | 277 | BOOL success = YES; 278 | for (NSString *className in groupList) { 279 | Class cls = NSClassFromString(className); 280 | if (cls == NULL) 281 | continue; 282 | 283 | if (class_respondsToSelector(object_getClass(cls), @selector(_ZK_unconditionallySwizzle))) { 284 | [cls _ZK_unconditionallySwizzle]; 285 | } else { 286 | success = NO; 287 | } 288 | } 289 | 290 | return success; 291 | } 292 | --------------------------------------------------------------------------------