Quellcode durchsuchen

备份 QQPlugin

mrabit vor 3 Jahren
Commit
42b2e03f7f
71 geänderte Dateien mit 4430 neuen und 0 gelöschten Zeilen
  1. 9 0
      .gitignore
  2. 21 0
      LICENSE
  3. 31 0
      Other/Install.sh
  4. 245 0
      Other/Products/Debug/QQPlugin.framework/Headers/QQPlugin.h
  5. 6 0
      Other/Products/Debug/QQPlugin.framework/Modules/module.modulemap
  6. BIN
      Other/Products/Debug/QQPlugin.framework/QQPlugin
  7. 44 0
      Other/Products/Debug/QQPlugin.framework/Resources/Info.plist
  8. BIN
      Other/Products/Debug/QQPlugin.framework/Resources/TKAutoReplyWindowController.nib
  9. 245 0
      Other/Products/Debug/QQPlugin.framework/Versions/A/Headers/QQPlugin.h
  10. 6 0
      Other/Products/Debug/QQPlugin.framework/Versions/A/Modules/module.modulemap
  11. BIN
      Other/Products/Debug/QQPlugin.framework/Versions/A/QQPlugin
  12. 44 0
      Other/Products/Debug/QQPlugin.framework/Versions/A/Resources/Info.plist
  13. BIN
      Other/Products/Debug/QQPlugin.framework/Versions/A/Resources/TKAutoReplyWindowController.nib
  14. 245 0
      Other/Products/Debug/QQPlugin.framework/Versions/Current/Headers/QQPlugin.h
  15. 6 0
      Other/Products/Debug/QQPlugin.framework/Versions/Current/Modules/module.modulemap
  16. BIN
      Other/Products/Debug/QQPlugin.framework/Versions/Current/QQPlugin
  17. 44 0
      Other/Products/Debug/QQPlugin.framework/Versions/Current/Resources/Info.plist
  18. BIN
      Other/Products/Debug/QQPlugin.framework/Versions/Current/Resources/TKAutoReplyWindowController.nib
  19. BIN
      Other/QQ Plugin.alfredworkflow
  20. BIN
      Other/ScreenShots/alfred_workflow.png
  21. BIN
      Other/ScreenShots/auto_reply.png
  22. BIN
      Other/ScreenShots/demo_alfred.gif
  23. BIN
      Other/ScreenShots/demo_reply_and_revoke.gif
  24. BIN
      Other/ScreenShots/revoke_msg.png
  25. 18 0
      Other/Uninstall.sh
  26. BIN
      Other/insert_dylib
  27. 6 0
      Podfile
  28. 608 0
      QQPlugin.xcodeproj/project.pbxproj
  29. 7 0
      QQPlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  30. 5 0
      QQPlugin.xcodeproj/xcuserdata/TK.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  31. 84 0
      QQPlugin.xcodeproj/xcuserdata/TK.xcuserdatad/xcschemes/QQPlugin.xcscheme
  32. 22 0
      QQPlugin.xcodeproj/xcuserdata/TK.xcuserdatad/xcschemes/xcschememanagement.plist
  33. 10 0
      QQPlugin.xcworkspace/contents.xcworkspacedata
  34. 8 0
      QQPlugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  35. 23 0
      QQPlugin.xcworkspace/xcuserdata/TK.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  36. 15 0
      QQPlugin/Category/QQ+hook.h
  37. 321 0
      QQPlugin/Category/QQ+hook.m
  38. 15 0
      QQPlugin/Common/Category/NSButton+Action.h
  39. 33 0
      QQPlugin/Common/Category/NSButton+Action.m
  40. 16 0
      QQPlugin/Common/Category/NSDate+Action.h
  41. 35 0
      QQPlugin/Common/Category/NSDate+Action.m
  42. 15 0
      QQPlugin/Common/Category/NSTextField+Action.h
  43. 28 0
      QQPlugin/Common/Category/NSTextField+Action.m
  44. 15 0
      QQPlugin/Common/Category/NSView+Action.h
  45. 20 0
      QQPlugin/Common/Category/NSView+Action.m
  46. 26 0
      QQPlugin/Common/Color.h
  47. 20 0
      QQPlugin/Config/TKQQPluginConfig.h
  48. 118 0
      QQPlugin/Config/TKQQPluginConfig.m
  49. 26 0
      QQPlugin/Info.plist
  50. 24 0
      QQPlugin/Model/TKAutoReplyModel.h
  51. 43 0
      QQPlugin/Model/TKAutoReplyModel.m
  52. 16 0
      QQPlugin/Model/TKBaseModel.h
  53. 22 0
      QQPlugin/Model/TKBaseModel.m
  54. 245 0
      QQPlugin/QQPlugin.h
  55. 34 0
      QQPlugin/Utils/TKHelper.h
  56. 45 0
      QQPlugin/Utils/TKHelper.m
  57. 15 0
      QQPlugin/Utils/TKMsgManager.h
  58. 37 0
      QQPlugin/Utils/TKMsgManager.m
  59. 18 0
      QQPlugin/Utils/TKWebServerManager.h
  60. 479 0
      QQPlugin/Utils/TKWebServerManager.m
  61. 210 0
      QQPlugin/Vendor/fishhook.c
  62. 76 0
      QQPlugin/Vendor/fishhook.h
  63. 17 0
      QQPlugin/Views/AutoReply/TKAutoReplyCell.h
  64. 91 0
      QQPlugin/Views/AutoReply/TKAutoReplyCell.m
  65. 17 0
      QQPlugin/Views/AutoReply/TKAutoReplyContentView.h
  66. 205 0
      QQPlugin/Views/AutoReply/TKAutoReplyContentView.m
  67. 16 0
      QQPlugin/WindowControllers/AutoReply/TKAutoReplyWindowController.h
  68. 239 0
      QQPlugin/WindowControllers/AutoReply/TKAutoReplyWindowController.m
  69. 31 0
      QQPlugin/WindowControllers/AutoReply/TKAutoReplyWindowController.xib
  70. 15 0
      QQPlugin/main.mm
  71. 95 0
      README.md

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+# Xcode
+#
+.DS_Store
+*.xcuserstate
+
+# CocoaPods
+#
+Pods/
+Podfile.lock

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 TK
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 31 - 0
Other/Install.sh

@@ -0,0 +1,31 @@
+#!/bin/bash
+
+app_name="QQ"
+shell_path="$(dirname "$0")"
+qq_path="/Applications/QQ.app"
+framework_name="QQPlugin"
+app_bundle_path="/Applications/${app_name}.app/Contents/MacOS"
+app_executable_path="${app_bundle_path}/${app_name}"
+app_executable_backup_path="${app_executable_path}_backup"
+framework_path="${app_bundle_path}/${framework_name}.framework"
+
+# 对 QQ 赋予权限
+if [ ! -w "$qq_path" ]
+then
+echo -e "\n\n为了将小助手写入QQ, 请输入密码 : "
+sudo chown -R $(whoami) "$qq_path"
+fi
+
+# 备份 QQ 原始可执行文件
+if [ ! -f "$app_executable_backup_path" ]
+then
+cp "$app_executable_path" "$app_executable_backup_path"
+result="y"
+else
+read -t 150 -p "已安装QQ小助手,是否覆盖?[y/n]:" result
+fi
+
+if [[ "$result" == 'y' ]]; then
+    cp -r "${shell_path}/Products/Debug/${framework_name}.framework" ${app_bundle_path}
+    ${shell_path}/insert_dylib --all-yes "${framework_path}/${framework_name}" "$app_executable_backup_path" "$app_executable_path"
+fi

+ 245 - 0
Other/Products/Debug/QQPlugin.framework/Headers/QQPlugin.h

@@ -0,0 +1,245 @@
+//
+//  QQPlugin.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "NSView+Action.h"
+#import "NSButton+Action.h"
+#import "NSTextField+Action.h"
+#import "NSDate+Action.h"
+#import "Color.h"
+#import <objc/runtime.h>
+
+#define IS_VALID_STRING(STR)          ((STR) && ![(STR) isEqualToString:@""])
+
+struct _BHMessageSession {
+    int _field1;
+    unsigned long long _field2;
+    unsigned long long _field3;
+    unsigned long long _field4;
+};
+
+@class BHFriendListManager, DiscussGroupInfo, BHGroupModel;
+
+#pragma mark - Model
+
+@interface MQBaseElem : NSObject
+{
+    NSString *_uinStr;
+}
+@end
+
+@interface Buddy : MQBaseElem
+{
+    BHFriendListManager *_friendListManager;
+}
+@end
+
+@interface Discuss : MQBaseElem
+@property(retain, nonatomic) DiscussGroupInfo *discussInfo;
+@property(nonatomic) unsigned long long discussSearchType;
+- (id)combinDiscussName;
+@end
+
+@interface Group : MQBaseElem
+@property(retain, nonatomic) BHGroupModel *troopModel;
+@end
+
+@interface DiscussGroupInfo : NSObject
+@property(retain, nonatomic) NSString *name;
+@property(nonatomic) int flag;
+@property(nonatomic) int memberNum;
+@property(nonatomic) long long groupUin;
+@end
+
+@interface DiscussMemberInfo : NSObject
+@property(retain, nonatomic) NSString *remarkName;
+@end
+
+@interface BHFriendGroupModel : NSObject
+@property(copy, nonatomic) NSString *groupName; 
+@end
+
+@interface BHGroupModel : NSObject
+@property(readonly, nonatomic) NSString *groupCode;
+@property(readonly, nonatomic) NSString *displayName;
+@property(copy, nonatomic) NSString *groupName;
+@property(copy, nonatomic) NSString *groupRemark;
+@property(nonatomic) unsigned long long groupMemberCount;
+@end
+
+@interface BHFontInfo : NSObject
+@end
+
+@interface BHMessageModel : NSObject
+@property(nonatomic) unsigned int time;
+@property(nonatomic) int msgID; 
+@property(retain, nonatomic) NSString *groupCode;
+@property(retain, nonatomic) NSString *discussGroupUin;
+@property(readonly) BOOL isSelfSend;
+@property(nonatomic) int msgSessionType;
+@property(nonatomic) int msgType;
+@property(retain, nonatomic) NSString *nickname;
+@property(copy, nonatomic) NSString *smallContent;
+@property(retain, nonatomic) NSString *uin;
+@property(readonly) NSString *senderUin;
+@property(copy, nonatomic) NSString *summaryTextContent;
+@property(readonly, nonatomic) NSArray *contentPartArray;
+- (id)senderDisplayName;
+- (int)chatType;
+@end
+
+@interface BHProfileModel : NSObject
+@property(copy, nonatomic) NSString *nick;
+@property(readonly) NSString *displayName;
+@property(copy, nonatomic) NSString *uin;
+@end
+
+@interface BHFriendModel : NSObject
+@property(copy, nonatomic) NSString *remark;
+@property(copy, nonatomic) NSString *uin;
+@property(retain, nonatomic) BHProfileModel *profileModel;
+@property(copy, nonatomic) NSString *groupID;
+@property(copy, nonatomic) NSString *showName;
+@end
+
+@interface BHTipsMsgOption : NSObject
+@property(nonatomic) BOOL addToDb;
+@end
+
+#pragma mark - ViewController
+@interface MQAIOChatViewController : NSObject
+- (void)revokeMessages:(id)arg1;
+@end
+
+#pragma mark - Controller
+@interface MainMenuController : NSObject
++ (id)sharedInstance;
+@end
+
+@interface AppController : NSObject
+- (void)notifyForceLogoutWithAccount:(id)arg1 type:(long long)arg2 tips:(id)arg3;
+- (void)notifyLoginWithAccount:(id)arg1 resultCode:(long long)arg2 userInfo:(id)arg3;
+@end
+
+#pragma mark - Manager
+@interface QQBaseSingleton : NSObject <NSCopying>
++ (id)sharedInstance;
+@end
+
+@interface BHMsgManager : NSObject
++ (id)sharedInstance;
+- (id)defaultFontInfo;
+- (void)appendReceiveMessageModel:(id)arg1 msgSource:(long long)arg2;
+- (void)addTipsMessage:(id)arg1 sessType:(int)arg2 uin:(id)arg3 option:(id)arg4;
+- (id)sendMessagePacket:(id)arg1 target:(struct _BHMessageSession)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+- (id)getImagePathByMsg:(id)arg1 imageSize:(long long)arg2;
+- (id)getShortVideoPathByMsg:(id)arg1;
+- (void)downloadImageByMsg:(id)arg1 content:(id)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+@end
+
+@interface MQAIOManager : NSObject
++ (id)sharedInstance;
+- (void)showAIOOfUin:(unsigned long long)arg1 chatType:(int)arg2 bringToTop:(BOOL)arg3;
+@end
+
+@interface BHAvatarManager : NSObject
++ (id)sharedInstance;
+- (id)userAvatarPathWithUIN:(id)arg1;
+- (id)groupAvatarPathWithGroupCode:(id)arg1;
+@end
+
+@interface BHDiscussGroupManager : NSObject
++ (id)sharedInstance;
+- (id)getDiscussMember:(id)arg1 memberUin:(long long)arg2;
+- (id)getGroupInfo:(long long)arg1;
+@end
+
+@interface BHFriendListManager : NSObject
+{
+    NSMutableDictionary *_friendCache;
+    NSMutableDictionary *_groupCache;
+}
+- (id)getFriendModelByUin:(id)arg1;
+@end
+
+@interface BHGroupManager : NSObject
+- (id)displayNameForGroupMemberWithGroupCode:(id)arg1 memberUin:(id)arg2;
+@end
+
+#pragma mark - Server
+@interface MsgDbService : NSObject
+- (void)updateQQMessageModel:(id)arg1 keyArray:(id)arg2;
+- (id)getMessageWithUin:(long long)arg1 sessType:(int)arg2 msgIds:(id)arg3;
+@end
+
+#pragma mark - Other
+@interface ContactSearcherInter : NSObject
+@property(retain, nonatomic) NSMutableArray <Buddy *> *searchedBuddys;
+@property(retain, nonatomic) NSMutableArray <Discuss *> *searchedDiscusses;
+@property(retain, nonatomic) NSMutableArray <Group *> *searchedGroups;
+- (void)Query:(id)arg1;
+@end
+
+@interface BHCompoundMessagePacket : NSObject
+@property(readonly, nonatomic) int msgType;
+@property(retain, nonatomic) BHFontInfo *fontInfo;
+- (void)addText:(id)arg1;
+- (id)initWithMessageType:(int)arg1;
+@end
+
+@interface MQRecentSessionManager : NSObject
++ (id)sharedLogicEngine;
+- (id)getSessionIDList;
+@end
+
+@interface GroupFolderManager : NSObject
++ (id)sharedFolderManager;
+- (id)BHGroupModelWithUin:(id)arg1;
+@end
+
+@interface BHProfileManager : NSObject
++ (id)sharedInstance;
+- (id)getProfileWithUIN:(id)arg1;
+@end
+
+@interface MQSessionID : NSObject <NSCopying>
++ (id)sessionIdWithChatType:(int)arg1 andUin:(unsigned long long)arg2;
+@property(readonly, nonatomic) int chatType; // @synthesize
+@property(readonly, nonatomic) unsigned long long uin; // @synthesize uin=_uin;
+- (id)uinString;
+@end
+
+@interface UnreadMsgMgr : NSObject
++ (id)sharedUnreadMsgMgr;
+- (id)getRecentModelFromSessionID:(id)arg1;
+@end
+
+@interface MQRecentMsgTips : NSObject
++ (id)tipsOfContentMsg:(id)arg1 sessionId:(id)arg2;
+@end
+
+@interface TXImageUtils : NSObject
++ (id)imageOfSession:(id)arg1;
+@end
+
+
+@interface TChatHistoryMsgManager : QQBaseSingleton
+- (id)getHistoryMsgModel:(long long)arg1 sessType:(int)arg2 filter:(unsigned long long)arg3;
+@end
+
+
+@interface TChatHistoryMsgModelWrapper : NSObject
+- (void)_loadMoreMessageUpForAllMsgType:(void(^)(void))arg1;
+@property(readonly, nonatomic) NSArray *msgArray;
+@end
+
+@interface VideoMsgLoadManager : NSObject
++ (void)requsetVideoMsgVideo:(id)arg1 completion:(id)arg2;
+@end
+
+

+ 6 - 0
Other/Products/Debug/QQPlugin.framework/Modules/module.modulemap

@@ -0,0 +1,6 @@
+framework module QQPlugin {
+  umbrella header "QQPlugin.h"
+
+  export *
+  module * { export * }
+}

BIN
Other/Products/Debug/QQPlugin.framework/QQPlugin


+ 44 - 0
Other/Products/Debug/QQPlugin.framework/Resources/Info.plist

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>BuildMachineOSBuild</key>
+	<string>18A391</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>QQPlugin</string>
+	<key>CFBundleIdentifier</key>
+	<string>tk.QQPlugin</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>QQPlugin</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.2</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>MacOSX</string>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>DTCompiler</key>
+	<string>com.apple.compilers.llvm.clang.1_0</string>
+	<key>DTPlatformBuild</key>
+	<string>10A255</string>
+	<key>DTPlatformVersion</key>
+	<string>GM</string>
+	<key>DTSDKBuild</key>
+	<string>18A384</string>
+	<key>DTSDKName</key>
+	<string>macosx10.14</string>
+	<key>DTXcode</key>
+	<string>1000</string>
+	<key>DTXcodeBuild</key>
+	<string>10A255</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2018年 TK. All rights reserved.</string>
+</dict>
+</plist>

BIN
Other/Products/Debug/QQPlugin.framework/Resources/TKAutoReplyWindowController.nib


+ 245 - 0
Other/Products/Debug/QQPlugin.framework/Versions/A/Headers/QQPlugin.h

@@ -0,0 +1,245 @@
+//
+//  QQPlugin.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "NSView+Action.h"
+#import "NSButton+Action.h"
+#import "NSTextField+Action.h"
+#import "NSDate+Action.h"
+#import "Color.h"
+#import <objc/runtime.h>
+
+#define IS_VALID_STRING(STR)          ((STR) && ![(STR) isEqualToString:@""])
+
+struct _BHMessageSession {
+    int _field1;
+    unsigned long long _field2;
+    unsigned long long _field3;
+    unsigned long long _field4;
+};
+
+@class BHFriendListManager, DiscussGroupInfo, BHGroupModel;
+
+#pragma mark - Model
+
+@interface MQBaseElem : NSObject
+{
+    NSString *_uinStr;
+}
+@end
+
+@interface Buddy : MQBaseElem
+{
+    BHFriendListManager *_friendListManager;
+}
+@end
+
+@interface Discuss : MQBaseElem
+@property(retain, nonatomic) DiscussGroupInfo *discussInfo;
+@property(nonatomic) unsigned long long discussSearchType;
+- (id)combinDiscussName;
+@end
+
+@interface Group : MQBaseElem
+@property(retain, nonatomic) BHGroupModel *troopModel;
+@end
+
+@interface DiscussGroupInfo : NSObject
+@property(retain, nonatomic) NSString *name;
+@property(nonatomic) int flag;
+@property(nonatomic) int memberNum;
+@property(nonatomic) long long groupUin;
+@end
+
+@interface DiscussMemberInfo : NSObject
+@property(retain, nonatomic) NSString *remarkName;
+@end
+
+@interface BHFriendGroupModel : NSObject
+@property(copy, nonatomic) NSString *groupName; 
+@end
+
+@interface BHGroupModel : NSObject
+@property(readonly, nonatomic) NSString *groupCode;
+@property(readonly, nonatomic) NSString *displayName;
+@property(copy, nonatomic) NSString *groupName;
+@property(copy, nonatomic) NSString *groupRemark;
+@property(nonatomic) unsigned long long groupMemberCount;
+@end
+
+@interface BHFontInfo : NSObject
+@end
+
+@interface BHMessageModel : NSObject
+@property(nonatomic) unsigned int time;
+@property(nonatomic) int msgID; 
+@property(retain, nonatomic) NSString *groupCode;
+@property(retain, nonatomic) NSString *discussGroupUin;
+@property(readonly) BOOL isSelfSend;
+@property(nonatomic) int msgSessionType;
+@property(nonatomic) int msgType;
+@property(retain, nonatomic) NSString *nickname;
+@property(copy, nonatomic) NSString *smallContent;
+@property(retain, nonatomic) NSString *uin;
+@property(readonly) NSString *senderUin;
+@property(copy, nonatomic) NSString *summaryTextContent;
+@property(readonly, nonatomic) NSArray *contentPartArray;
+- (id)senderDisplayName;
+- (int)chatType;
+@end
+
+@interface BHProfileModel : NSObject
+@property(copy, nonatomic) NSString *nick;
+@property(readonly) NSString *displayName;
+@property(copy, nonatomic) NSString *uin;
+@end
+
+@interface BHFriendModel : NSObject
+@property(copy, nonatomic) NSString *remark;
+@property(copy, nonatomic) NSString *uin;
+@property(retain, nonatomic) BHProfileModel *profileModel;
+@property(copy, nonatomic) NSString *groupID;
+@property(copy, nonatomic) NSString *showName;
+@end
+
+@interface BHTipsMsgOption : NSObject
+@property(nonatomic) BOOL addToDb;
+@end
+
+#pragma mark - ViewController
+@interface MQAIOChatViewController : NSObject
+- (void)revokeMessages:(id)arg1;
+@end
+
+#pragma mark - Controller
+@interface MainMenuController : NSObject
++ (id)sharedInstance;
+@end
+
+@interface AppController : NSObject
+- (void)notifyForceLogoutWithAccount:(id)arg1 type:(long long)arg2 tips:(id)arg3;
+- (void)notifyLoginWithAccount:(id)arg1 resultCode:(long long)arg2 userInfo:(id)arg3;
+@end
+
+#pragma mark - Manager
+@interface QQBaseSingleton : NSObject <NSCopying>
++ (id)sharedInstance;
+@end
+
+@interface BHMsgManager : NSObject
++ (id)sharedInstance;
+- (id)defaultFontInfo;
+- (void)appendReceiveMessageModel:(id)arg1 msgSource:(long long)arg2;
+- (void)addTipsMessage:(id)arg1 sessType:(int)arg2 uin:(id)arg3 option:(id)arg4;
+- (id)sendMessagePacket:(id)arg1 target:(struct _BHMessageSession)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+- (id)getImagePathByMsg:(id)arg1 imageSize:(long long)arg2;
+- (id)getShortVideoPathByMsg:(id)arg1;
+- (void)downloadImageByMsg:(id)arg1 content:(id)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+@end
+
+@interface MQAIOManager : NSObject
++ (id)sharedInstance;
+- (void)showAIOOfUin:(unsigned long long)arg1 chatType:(int)arg2 bringToTop:(BOOL)arg3;
+@end
+
+@interface BHAvatarManager : NSObject
++ (id)sharedInstance;
+- (id)userAvatarPathWithUIN:(id)arg1;
+- (id)groupAvatarPathWithGroupCode:(id)arg1;
+@end
+
+@interface BHDiscussGroupManager : NSObject
++ (id)sharedInstance;
+- (id)getDiscussMember:(id)arg1 memberUin:(long long)arg2;
+- (id)getGroupInfo:(long long)arg1;
+@end
+
+@interface BHFriendListManager : NSObject
+{
+    NSMutableDictionary *_friendCache;
+    NSMutableDictionary *_groupCache;
+}
+- (id)getFriendModelByUin:(id)arg1;
+@end
+
+@interface BHGroupManager : NSObject
+- (id)displayNameForGroupMemberWithGroupCode:(id)arg1 memberUin:(id)arg2;
+@end
+
+#pragma mark - Server
+@interface MsgDbService : NSObject
+- (void)updateQQMessageModel:(id)arg1 keyArray:(id)arg2;
+- (id)getMessageWithUin:(long long)arg1 sessType:(int)arg2 msgIds:(id)arg3;
+@end
+
+#pragma mark - Other
+@interface ContactSearcherInter : NSObject
+@property(retain, nonatomic) NSMutableArray <Buddy *> *searchedBuddys;
+@property(retain, nonatomic) NSMutableArray <Discuss *> *searchedDiscusses;
+@property(retain, nonatomic) NSMutableArray <Group *> *searchedGroups;
+- (void)Query:(id)arg1;
+@end
+
+@interface BHCompoundMessagePacket : NSObject
+@property(readonly, nonatomic) int msgType;
+@property(retain, nonatomic) BHFontInfo *fontInfo;
+- (void)addText:(id)arg1;
+- (id)initWithMessageType:(int)arg1;
+@end
+
+@interface MQRecentSessionManager : NSObject
++ (id)sharedLogicEngine;
+- (id)getSessionIDList;
+@end
+
+@interface GroupFolderManager : NSObject
++ (id)sharedFolderManager;
+- (id)BHGroupModelWithUin:(id)arg1;
+@end
+
+@interface BHProfileManager : NSObject
++ (id)sharedInstance;
+- (id)getProfileWithUIN:(id)arg1;
+@end
+
+@interface MQSessionID : NSObject <NSCopying>
++ (id)sessionIdWithChatType:(int)arg1 andUin:(unsigned long long)arg2;
+@property(readonly, nonatomic) int chatType; // @synthesize
+@property(readonly, nonatomic) unsigned long long uin; // @synthesize uin=_uin;
+- (id)uinString;
+@end
+
+@interface UnreadMsgMgr : NSObject
++ (id)sharedUnreadMsgMgr;
+- (id)getRecentModelFromSessionID:(id)arg1;
+@end
+
+@interface MQRecentMsgTips : NSObject
++ (id)tipsOfContentMsg:(id)arg1 sessionId:(id)arg2;
+@end
+
+@interface TXImageUtils : NSObject
++ (id)imageOfSession:(id)arg1;
+@end
+
+
+@interface TChatHistoryMsgManager : QQBaseSingleton
+- (id)getHistoryMsgModel:(long long)arg1 sessType:(int)arg2 filter:(unsigned long long)arg3;
+@end
+
+
+@interface TChatHistoryMsgModelWrapper : NSObject
+- (void)_loadMoreMessageUpForAllMsgType:(void(^)(void))arg1;
+@property(readonly, nonatomic) NSArray *msgArray;
+@end
+
+@interface VideoMsgLoadManager : NSObject
++ (void)requsetVideoMsgVideo:(id)arg1 completion:(id)arg2;
+@end
+
+

+ 6 - 0
Other/Products/Debug/QQPlugin.framework/Versions/A/Modules/module.modulemap

@@ -0,0 +1,6 @@
+framework module QQPlugin {
+  umbrella header "QQPlugin.h"
+
+  export *
+  module * { export * }
+}

BIN
Other/Products/Debug/QQPlugin.framework/Versions/A/QQPlugin


+ 44 - 0
Other/Products/Debug/QQPlugin.framework/Versions/A/Resources/Info.plist

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>BuildMachineOSBuild</key>
+	<string>18A391</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>QQPlugin</string>
+	<key>CFBundleIdentifier</key>
+	<string>tk.QQPlugin</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>QQPlugin</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.2</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>MacOSX</string>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>DTCompiler</key>
+	<string>com.apple.compilers.llvm.clang.1_0</string>
+	<key>DTPlatformBuild</key>
+	<string>10A255</string>
+	<key>DTPlatformVersion</key>
+	<string>GM</string>
+	<key>DTSDKBuild</key>
+	<string>18A384</string>
+	<key>DTSDKName</key>
+	<string>macosx10.14</string>
+	<key>DTXcode</key>
+	<string>1000</string>
+	<key>DTXcodeBuild</key>
+	<string>10A255</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2018年 TK. All rights reserved.</string>
+</dict>
+</plist>

BIN
Other/Products/Debug/QQPlugin.framework/Versions/A/Resources/TKAutoReplyWindowController.nib


+ 245 - 0
Other/Products/Debug/QQPlugin.framework/Versions/Current/Headers/QQPlugin.h

@@ -0,0 +1,245 @@
+//
+//  QQPlugin.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "NSView+Action.h"
+#import "NSButton+Action.h"
+#import "NSTextField+Action.h"
+#import "NSDate+Action.h"
+#import "Color.h"
+#import <objc/runtime.h>
+
+#define IS_VALID_STRING(STR)          ((STR) && ![(STR) isEqualToString:@""])
+
+struct _BHMessageSession {
+    int _field1;
+    unsigned long long _field2;
+    unsigned long long _field3;
+    unsigned long long _field4;
+};
+
+@class BHFriendListManager, DiscussGroupInfo, BHGroupModel;
+
+#pragma mark - Model
+
+@interface MQBaseElem : NSObject
+{
+    NSString *_uinStr;
+}
+@end
+
+@interface Buddy : MQBaseElem
+{
+    BHFriendListManager *_friendListManager;
+}
+@end
+
+@interface Discuss : MQBaseElem
+@property(retain, nonatomic) DiscussGroupInfo *discussInfo;
+@property(nonatomic) unsigned long long discussSearchType;
+- (id)combinDiscussName;
+@end
+
+@interface Group : MQBaseElem
+@property(retain, nonatomic) BHGroupModel *troopModel;
+@end
+
+@interface DiscussGroupInfo : NSObject
+@property(retain, nonatomic) NSString *name;
+@property(nonatomic) int flag;
+@property(nonatomic) int memberNum;
+@property(nonatomic) long long groupUin;
+@end
+
+@interface DiscussMemberInfo : NSObject
+@property(retain, nonatomic) NSString *remarkName;
+@end
+
+@interface BHFriendGroupModel : NSObject
+@property(copy, nonatomic) NSString *groupName; 
+@end
+
+@interface BHGroupModel : NSObject
+@property(readonly, nonatomic) NSString *groupCode;
+@property(readonly, nonatomic) NSString *displayName;
+@property(copy, nonatomic) NSString *groupName;
+@property(copy, nonatomic) NSString *groupRemark;
+@property(nonatomic) unsigned long long groupMemberCount;
+@end
+
+@interface BHFontInfo : NSObject
+@end
+
+@interface BHMessageModel : NSObject
+@property(nonatomic) unsigned int time;
+@property(nonatomic) int msgID; 
+@property(retain, nonatomic) NSString *groupCode;
+@property(retain, nonatomic) NSString *discussGroupUin;
+@property(readonly) BOOL isSelfSend;
+@property(nonatomic) int msgSessionType;
+@property(nonatomic) int msgType;
+@property(retain, nonatomic) NSString *nickname;
+@property(copy, nonatomic) NSString *smallContent;
+@property(retain, nonatomic) NSString *uin;
+@property(readonly) NSString *senderUin;
+@property(copy, nonatomic) NSString *summaryTextContent;
+@property(readonly, nonatomic) NSArray *contentPartArray;
+- (id)senderDisplayName;
+- (int)chatType;
+@end
+
+@interface BHProfileModel : NSObject
+@property(copy, nonatomic) NSString *nick;
+@property(readonly) NSString *displayName;
+@property(copy, nonatomic) NSString *uin;
+@end
+
+@interface BHFriendModel : NSObject
+@property(copy, nonatomic) NSString *remark;
+@property(copy, nonatomic) NSString *uin;
+@property(retain, nonatomic) BHProfileModel *profileModel;
+@property(copy, nonatomic) NSString *groupID;
+@property(copy, nonatomic) NSString *showName;
+@end
+
+@interface BHTipsMsgOption : NSObject
+@property(nonatomic) BOOL addToDb;
+@end
+
+#pragma mark - ViewController
+@interface MQAIOChatViewController : NSObject
+- (void)revokeMessages:(id)arg1;
+@end
+
+#pragma mark - Controller
+@interface MainMenuController : NSObject
++ (id)sharedInstance;
+@end
+
+@interface AppController : NSObject
+- (void)notifyForceLogoutWithAccount:(id)arg1 type:(long long)arg2 tips:(id)arg3;
+- (void)notifyLoginWithAccount:(id)arg1 resultCode:(long long)arg2 userInfo:(id)arg3;
+@end
+
+#pragma mark - Manager
+@interface QQBaseSingleton : NSObject <NSCopying>
++ (id)sharedInstance;
+@end
+
+@interface BHMsgManager : NSObject
++ (id)sharedInstance;
+- (id)defaultFontInfo;
+- (void)appendReceiveMessageModel:(id)arg1 msgSource:(long long)arg2;
+- (void)addTipsMessage:(id)arg1 sessType:(int)arg2 uin:(id)arg3 option:(id)arg4;
+- (id)sendMessagePacket:(id)arg1 target:(struct _BHMessageSession)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+- (id)getImagePathByMsg:(id)arg1 imageSize:(long long)arg2;
+- (id)getShortVideoPathByMsg:(id)arg1;
+- (void)downloadImageByMsg:(id)arg1 content:(id)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+@end
+
+@interface MQAIOManager : NSObject
++ (id)sharedInstance;
+- (void)showAIOOfUin:(unsigned long long)arg1 chatType:(int)arg2 bringToTop:(BOOL)arg3;
+@end
+
+@interface BHAvatarManager : NSObject
++ (id)sharedInstance;
+- (id)userAvatarPathWithUIN:(id)arg1;
+- (id)groupAvatarPathWithGroupCode:(id)arg1;
+@end
+
+@interface BHDiscussGroupManager : NSObject
++ (id)sharedInstance;
+- (id)getDiscussMember:(id)arg1 memberUin:(long long)arg2;
+- (id)getGroupInfo:(long long)arg1;
+@end
+
+@interface BHFriendListManager : NSObject
+{
+    NSMutableDictionary *_friendCache;
+    NSMutableDictionary *_groupCache;
+}
+- (id)getFriendModelByUin:(id)arg1;
+@end
+
+@interface BHGroupManager : NSObject
+- (id)displayNameForGroupMemberWithGroupCode:(id)arg1 memberUin:(id)arg2;
+@end
+
+#pragma mark - Server
+@interface MsgDbService : NSObject
+- (void)updateQQMessageModel:(id)arg1 keyArray:(id)arg2;
+- (id)getMessageWithUin:(long long)arg1 sessType:(int)arg2 msgIds:(id)arg3;
+@end
+
+#pragma mark - Other
+@interface ContactSearcherInter : NSObject
+@property(retain, nonatomic) NSMutableArray <Buddy *> *searchedBuddys;
+@property(retain, nonatomic) NSMutableArray <Discuss *> *searchedDiscusses;
+@property(retain, nonatomic) NSMutableArray <Group *> *searchedGroups;
+- (void)Query:(id)arg1;
+@end
+
+@interface BHCompoundMessagePacket : NSObject
+@property(readonly, nonatomic) int msgType;
+@property(retain, nonatomic) BHFontInfo *fontInfo;
+- (void)addText:(id)arg1;
+- (id)initWithMessageType:(int)arg1;
+@end
+
+@interface MQRecentSessionManager : NSObject
++ (id)sharedLogicEngine;
+- (id)getSessionIDList;
+@end
+
+@interface GroupFolderManager : NSObject
++ (id)sharedFolderManager;
+- (id)BHGroupModelWithUin:(id)arg1;
+@end
+
+@interface BHProfileManager : NSObject
++ (id)sharedInstance;
+- (id)getProfileWithUIN:(id)arg1;
+@end
+
+@interface MQSessionID : NSObject <NSCopying>
++ (id)sessionIdWithChatType:(int)arg1 andUin:(unsigned long long)arg2;
+@property(readonly, nonatomic) int chatType; // @synthesize
+@property(readonly, nonatomic) unsigned long long uin; // @synthesize uin=_uin;
+- (id)uinString;
+@end
+
+@interface UnreadMsgMgr : NSObject
++ (id)sharedUnreadMsgMgr;
+- (id)getRecentModelFromSessionID:(id)arg1;
+@end
+
+@interface MQRecentMsgTips : NSObject
++ (id)tipsOfContentMsg:(id)arg1 sessionId:(id)arg2;
+@end
+
+@interface TXImageUtils : NSObject
++ (id)imageOfSession:(id)arg1;
+@end
+
+
+@interface TChatHistoryMsgManager : QQBaseSingleton
+- (id)getHistoryMsgModel:(long long)arg1 sessType:(int)arg2 filter:(unsigned long long)arg3;
+@end
+
+
+@interface TChatHistoryMsgModelWrapper : NSObject
+- (void)_loadMoreMessageUpForAllMsgType:(void(^)(void))arg1;
+@property(readonly, nonatomic) NSArray *msgArray;
+@end
+
+@interface VideoMsgLoadManager : NSObject
++ (void)requsetVideoMsgVideo:(id)arg1 completion:(id)arg2;
+@end
+
+

+ 6 - 0
Other/Products/Debug/QQPlugin.framework/Versions/Current/Modules/module.modulemap

@@ -0,0 +1,6 @@
+framework module QQPlugin {
+  umbrella header "QQPlugin.h"
+
+  export *
+  module * { export * }
+}

BIN
Other/Products/Debug/QQPlugin.framework/Versions/Current/QQPlugin


+ 44 - 0
Other/Products/Debug/QQPlugin.framework/Versions/Current/Resources/Info.plist

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>BuildMachineOSBuild</key>
+	<string>18A391</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>QQPlugin</string>
+	<key>CFBundleIdentifier</key>
+	<string>tk.QQPlugin</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>QQPlugin</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.2</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>MacOSX</string>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>DTCompiler</key>
+	<string>com.apple.compilers.llvm.clang.1_0</string>
+	<key>DTPlatformBuild</key>
+	<string>10A255</string>
+	<key>DTPlatformVersion</key>
+	<string>GM</string>
+	<key>DTSDKBuild</key>
+	<string>18A384</string>
+	<key>DTSDKName</key>
+	<string>macosx10.14</string>
+	<key>DTXcode</key>
+	<string>1000</string>
+	<key>DTXcodeBuild</key>
+	<string>10A255</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2018年 TK. All rights reserved.</string>
+</dict>
+</plist>

BIN
Other/Products/Debug/QQPlugin.framework/Versions/Current/Resources/TKAutoReplyWindowController.nib


BIN
Other/QQ Plugin.alfredworkflow


BIN
Other/ScreenShots/alfred_workflow.png


BIN
Other/ScreenShots/auto_reply.png


BIN
Other/ScreenShots/demo_alfred.gif


BIN
Other/ScreenShots/demo_reply_and_revoke.gif


BIN
Other/ScreenShots/revoke_msg.png


+ 18 - 0
Other/Uninstall.sh

@@ -0,0 +1,18 @@
+# !/bin/bash
+
+app_name="QQ"
+framework_name="QQPlugin"
+app_bundle_path="/Applications/${app_name}.app/Contents/MacOS"
+app_executable_path="${app_bundle_path}/${app_name}"
+app_executable_backup_path="${app_executable_path}_backup"
+framework_path="${app_bundle_path}/${framework_name}.framework"
+# 备份QQ原始可执行文件
+if [ -f "$app_executable_backup_path" ]
+then
+rm "$app_executable_path"
+rm -rf "$framework_path"
+mv "$app_executable_backup_path" "$app_executable_path"
+echo "\n\t卸载成功"
+else
+echo "\n\t未发现QQ小助手"
+fi

BIN
Other/insert_dylib


+ 6 - 0
Podfile

@@ -0,0 +1,6 @@
+platform :osx, '10.12'
+inhibit_all_warnings!
+
+target 'QQPlugin' do
+  pod 'GCDWebServer', '~> 3.4.2'
+end

+ 608 - 0
QQPlugin.xcodeproj/project.pbxproj

@@ -0,0 +1,608 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 48;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		28E76C707D05B519FEBBAD83 /* libPods-QQPlugin.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DDF6A4B0BE7FF48B46901D87 /* libPods-QQPlugin.a */; };
+		528DA5D6205F80780063E2FC /* QQPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 528DA5D4205F80780063E2FC /* QQPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		528DA5DF205F80870063E2FC /* QQ+hook.m in Sources */ = {isa = PBXBuildFile; fileRef = 528DA5DD205F80870063E2FC /* QQ+hook.m */; };
+		528DA5E0205F80870063E2FC /* QQ+hook.h in Headers */ = {isa = PBXBuildFile; fileRef = 528DA5DE205F80870063E2FC /* QQ+hook.h */; };
+		528DA5EA205F809C0063E2FC /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 528DA5E9205F809C0063E2FC /* main.mm */; };
+		528DA5EC205F81C40063E2FC /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 528DA5E7205F80900063E2FC /* fishhook.c */; };
+		528DA5F0205F82430063E2FC /* TKQQPluginConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 528DA5EE205F82430063E2FC /* TKQQPluginConfig.h */; };
+		528DA5F1205F82430063E2FC /* TKQQPluginConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 528DA5EF205F82430063E2FC /* TKQQPluginConfig.m */; };
+		528DA5FB206003CD0063E2FC /* TKHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 528DA5F3206003CC0063E2FC /* TKHelper.h */; };
+		528DA5FF206003CD0063E2FC /* TKHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 528DA5F7206003CC0063E2FC /* TKHelper.m */; };
+		529184C32126573C00FE4B57 /* NSDate+Action.m in Sources */ = {isa = PBXBuildFile; fileRef = 529184C12126573C00FE4B57 /* NSDate+Action.m */; };
+		529184C42126573C00FE4B57 /* NSDate+Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 529184C22126573C00FE4B57 /* NSDate+Action.h */; };
+		529F91DC206BCF680026430E /* TKWebServerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 529F91DA206BCF680026430E /* TKWebServerManager.m */; };
+		529F91DD206BCF680026430E /* TKWebServerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 529F91DB206BCF680026430E /* TKWebServerManager.h */; };
+		529F91E0206BF0550026430E /* TKMsgManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 529F91DE206BF0550026430E /* TKMsgManager.h */; };
+		529F91E1206BF0550026430E /* TKMsgManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 529F91DF206BF0550026430E /* TKMsgManager.m */; };
+		52E3A45F206AABF00048C592 /* TKAutoReplyModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A45D206AABF00048C592 /* TKAutoReplyModel.h */; };
+		52E3A460206AABF00048C592 /* TKAutoReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A45E206AABF00048C592 /* TKAutoReplyModel.m */; };
+		52E3A463206AABFB0048C592 /* TKBaseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A461206AABFA0048C592 /* TKBaseModel.m */; };
+		52E3A464206AABFB0048C592 /* TKBaseModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A462206AABFA0048C592 /* TKBaseModel.h */; };
+		52E3A46E206AACDF0048C592 /* TKAutoReplyWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A467206AACDF0048C592 /* TKAutoReplyWindowController.h */; };
+		52E3A46F206AACDF0048C592 /* TKAutoReplyWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 52E3A468206AACDF0048C592 /* TKAutoReplyWindowController.xib */; };
+		52E3A470206AACDF0048C592 /* TKAutoReplyWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A469206AACDF0048C592 /* TKAutoReplyWindowController.m */; };
+		52E3A47D206AAD020048C592 /* TKAutoReplyContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A476206AAD020048C592 /* TKAutoReplyContentView.m */; };
+		52E3A47E206AAD020048C592 /* TKAutoReplyCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A477206AAD020048C592 /* TKAutoReplyCell.h */; };
+		52E3A47F206AAD020048C592 /* TKAutoReplyContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A478206AAD020048C592 /* TKAutoReplyContentView.h */; };
+		52E3A480206AAD020048C592 /* TKAutoReplyCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A479206AAD020048C592 /* TKAutoReplyCell.m */; };
+		52E3A48D206AAD390048C592 /* NSButton+Action.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A485206AAD390048C592 /* NSButton+Action.m */; };
+		52E3A48E206AAD390048C592 /* NSTextField+Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A486206AAD390048C592 /* NSTextField+Action.h */; };
+		52E3A48F206AAD390048C592 /* NSView+Action.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A487206AAD390048C592 /* NSView+Action.m */; };
+		52E3A490206AAD390048C592 /* NSButton+Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A488206AAD390048C592 /* NSButton+Action.h */; };
+		52E3A491206AAD390048C592 /* NSView+Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A489206AAD390048C592 /* NSView+Action.h */; };
+		52E3A492206AAD390048C592 /* NSTextField+Action.m in Sources */ = {isa = PBXBuildFile; fileRef = 52E3A48A206AAD390048C592 /* NSTextField+Action.m */; };
+		52E3A493206AAD390048C592 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E3A48B206AAD390048C592 /* Color.h */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		39FA9A25B25855D7E65B80C4 /* Pods-QQPlugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QQPlugin.debug.xcconfig"; path = "Pods/Target Support Files/Pods-QQPlugin/Pods-QQPlugin.debug.xcconfig"; sourceTree = "<group>"; };
+		4AB8D9FD2365E53D0213EE80 /* Pods-QQPlugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QQPlugin.release.xcconfig"; path = "Pods/Target Support Files/Pods-QQPlugin/Pods-QQPlugin.release.xcconfig"; sourceTree = "<group>"; };
+		528DA5D1205F80780063E2FC /* QQPlugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QQPlugin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		528DA5D4205F80780063E2FC /* QQPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QQPlugin.h; sourceTree = "<group>"; };
+		528DA5D5205F80780063E2FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		528DA5DD205F80870063E2FC /* QQ+hook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "QQ+hook.m"; sourceTree = "<group>"; };
+		528DA5DE205F80870063E2FC /* QQ+hook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "QQ+hook.h"; sourceTree = "<group>"; };
+		528DA5E7205F80900063E2FC /* fishhook.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = "<group>"; };
+		528DA5E8205F80900063E2FC /* fishhook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = "<group>"; };
+		528DA5E9205F809C0063E2FC /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
+		528DA5EE205F82430063E2FC /* TKQQPluginConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TKQQPluginConfig.h; sourceTree = "<group>"; };
+		528DA5EF205F82430063E2FC /* TKQQPluginConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TKQQPluginConfig.m; sourceTree = "<group>"; };
+		528DA5F3206003CC0063E2FC /* TKHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKHelper.h; sourceTree = "<group>"; };
+		528DA5F7206003CC0063E2FC /* TKHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKHelper.m; sourceTree = "<group>"; };
+		529184C12126573C00FE4B57 /* NSDate+Action.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Action.m"; sourceTree = "<group>"; };
+		529184C22126573C00FE4B57 /* NSDate+Action.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Action.h"; sourceTree = "<group>"; };
+		529F91DA206BCF680026430E /* TKWebServerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKWebServerManager.m; sourceTree = "<group>"; };
+		529F91DB206BCF680026430E /* TKWebServerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKWebServerManager.h; sourceTree = "<group>"; };
+		529F91DE206BF0550026430E /* TKMsgManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TKMsgManager.h; sourceTree = "<group>"; };
+		529F91DF206BF0550026430E /* TKMsgManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TKMsgManager.m; sourceTree = "<group>"; };
+		52E3A45D206AABF00048C592 /* TKAutoReplyModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKAutoReplyModel.h; sourceTree = "<group>"; };
+		52E3A45E206AABF00048C592 /* TKAutoReplyModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKAutoReplyModel.m; sourceTree = "<group>"; };
+		52E3A461206AABFA0048C592 /* TKBaseModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKBaseModel.m; sourceTree = "<group>"; };
+		52E3A462206AABFA0048C592 /* TKBaseModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKBaseModel.h; sourceTree = "<group>"; };
+		52E3A467206AACDF0048C592 /* TKAutoReplyWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKAutoReplyWindowController.h; sourceTree = "<group>"; };
+		52E3A468206AACDF0048C592 /* TKAutoReplyWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TKAutoReplyWindowController.xib; sourceTree = "<group>"; };
+		52E3A469206AACDF0048C592 /* TKAutoReplyWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKAutoReplyWindowController.m; sourceTree = "<group>"; };
+		52E3A476206AAD020048C592 /* TKAutoReplyContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKAutoReplyContentView.m; sourceTree = "<group>"; };
+		52E3A477206AAD020048C592 /* TKAutoReplyCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKAutoReplyCell.h; sourceTree = "<group>"; };
+		52E3A478206AAD020048C592 /* TKAutoReplyContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TKAutoReplyContentView.h; sourceTree = "<group>"; };
+		52E3A479206AAD020048C592 /* TKAutoReplyCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TKAutoReplyCell.m; sourceTree = "<group>"; };
+		52E3A485206AAD390048C592 /* NSButton+Action.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSButton+Action.m"; sourceTree = "<group>"; };
+		52E3A486206AAD390048C592 /* NSTextField+Action.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTextField+Action.h"; sourceTree = "<group>"; };
+		52E3A487206AAD390048C592 /* NSView+Action.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+Action.m"; sourceTree = "<group>"; };
+		52E3A488206AAD390048C592 /* NSButton+Action.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSButton+Action.h"; sourceTree = "<group>"; };
+		52E3A489206AAD390048C592 /* NSView+Action.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSView+Action.h"; sourceTree = "<group>"; };
+		52E3A48A206AAD390048C592 /* NSTextField+Action.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTextField+Action.m"; sourceTree = "<group>"; };
+		52E3A48B206AAD390048C592 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
+		DDF6A4B0BE7FF48B46901D87 /* libPods-QQPlugin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-QQPlugin.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		528DA5CD205F80780063E2FC /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				28E76C707D05B519FEBBAD83 /* libPods-QQPlugin.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		528DA5C7205F80780063E2FC = {
+			isa = PBXGroup;
+			children = (
+				528DA5D3205F80780063E2FC /* QQPlugin */,
+				528DA5D2205F80780063E2FC /* Products */,
+				DDF10C314AE4CC5029B49606 /* Pods */,
+				966DFD8FE1117A663093B76F /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		528DA5D2205F80780063E2FC /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				528DA5D1205F80780063E2FC /* QQPlugin.framework */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		528DA5D3205F80780063E2FC /* QQPlugin */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A483206AAD390048C592 /* Common */,
+				52E3A474206AAD020048C592 /* Views */,
+				52E3A465206AACDF0048C592 /* WindowControllers */,
+				52E3A45C206AABD90048C592 /* Model */,
+				528DA5F2206003CC0063E2FC /* Utils */,
+				528DA5ED205F822B0063E2FC /* Config */,
+				528DA5E6205F80900063E2FC /* Vendor */,
+				528DA5DC205F80870063E2FC /* Category */,
+				528DA5D4205F80780063E2FC /* QQPlugin.h */,
+				528DA5E9205F809C0063E2FC /* main.mm */,
+				528DA5D5205F80780063E2FC /* Info.plist */,
+			);
+			path = QQPlugin;
+			sourceTree = "<group>";
+		};
+		528DA5DC205F80870063E2FC /* Category */ = {
+			isa = PBXGroup;
+			children = (
+				528DA5DE205F80870063E2FC /* QQ+hook.h */,
+				528DA5DD205F80870063E2FC /* QQ+hook.m */,
+			);
+			path = Category;
+			sourceTree = "<group>";
+		};
+		528DA5E6205F80900063E2FC /* Vendor */ = {
+			isa = PBXGroup;
+			children = (
+				528DA5E7205F80900063E2FC /* fishhook.c */,
+				528DA5E8205F80900063E2FC /* fishhook.h */,
+			);
+			path = Vendor;
+			sourceTree = "<group>";
+		};
+		528DA5ED205F822B0063E2FC /* Config */ = {
+			isa = PBXGroup;
+			children = (
+				528DA5EE205F82430063E2FC /* TKQQPluginConfig.h */,
+				528DA5EF205F82430063E2FC /* TKQQPluginConfig.m */,
+			);
+			path = Config;
+			sourceTree = "<group>";
+		};
+		528DA5F2206003CC0063E2FC /* Utils */ = {
+			isa = PBXGroup;
+			children = (
+				528DA5F3206003CC0063E2FC /* TKHelper.h */,
+				528DA5F7206003CC0063E2FC /* TKHelper.m */,
+				529F91DB206BCF680026430E /* TKWebServerManager.h */,
+				529F91DA206BCF680026430E /* TKWebServerManager.m */,
+				529F91DE206BF0550026430E /* TKMsgManager.h */,
+				529F91DF206BF0550026430E /* TKMsgManager.m */,
+			);
+			path = Utils;
+			sourceTree = "<group>";
+		};
+		52E3A45C206AABD90048C592 /* Model */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A462206AABFA0048C592 /* TKBaseModel.h */,
+				52E3A461206AABFA0048C592 /* TKBaseModel.m */,
+				52E3A45D206AABF00048C592 /* TKAutoReplyModel.h */,
+				52E3A45E206AABF00048C592 /* TKAutoReplyModel.m */,
+			);
+			path = Model;
+			sourceTree = "<group>";
+		};
+		52E3A465206AACDF0048C592 /* WindowControllers */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A466206AACDF0048C592 /* AutoReply */,
+			);
+			path = WindowControllers;
+			sourceTree = "<group>";
+		};
+		52E3A466206AACDF0048C592 /* AutoReply */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A467206AACDF0048C592 /* TKAutoReplyWindowController.h */,
+				52E3A468206AACDF0048C592 /* TKAutoReplyWindowController.xib */,
+				52E3A469206AACDF0048C592 /* TKAutoReplyWindowController.m */,
+			);
+			path = AutoReply;
+			sourceTree = "<group>";
+		};
+		52E3A474206AAD020048C592 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A475206AAD020048C592 /* AutoReply */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+		52E3A475206AAD020048C592 /* AutoReply */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A478206AAD020048C592 /* TKAutoReplyContentView.h */,
+				52E3A476206AAD020048C592 /* TKAutoReplyContentView.m */,
+				52E3A477206AAD020048C592 /* TKAutoReplyCell.h */,
+				52E3A479206AAD020048C592 /* TKAutoReplyCell.m */,
+			);
+			path = AutoReply;
+			sourceTree = "<group>";
+		};
+		52E3A483206AAD390048C592 /* Common */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A484206AAD390048C592 /* Category */,
+				52E3A48B206AAD390048C592 /* Color.h */,
+			);
+			path = Common;
+			sourceTree = "<group>";
+		};
+		52E3A484206AAD390048C592 /* Category */ = {
+			isa = PBXGroup;
+			children = (
+				52E3A489206AAD390048C592 /* NSView+Action.h */,
+				52E3A487206AAD390048C592 /* NSView+Action.m */,
+				52E3A488206AAD390048C592 /* NSButton+Action.h */,
+				52E3A485206AAD390048C592 /* NSButton+Action.m */,
+				52E3A486206AAD390048C592 /* NSTextField+Action.h */,
+				52E3A48A206AAD390048C592 /* NSTextField+Action.m */,
+				529184C22126573C00FE4B57 /* NSDate+Action.h */,
+				529184C12126573C00FE4B57 /* NSDate+Action.m */,
+			);
+			path = Category;
+			sourceTree = "<group>";
+		};
+		966DFD8FE1117A663093B76F /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				DDF6A4B0BE7FF48B46901D87 /* libPods-QQPlugin.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		DDF10C314AE4CC5029B49606 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				39FA9A25B25855D7E65B80C4 /* Pods-QQPlugin.debug.xcconfig */,
+				4AB8D9FD2365E53D0213EE80 /* Pods-QQPlugin.release.xcconfig */,
+			);
+			name = Pods;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		528DA5CE205F80780063E2FC /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				529F91E0206BF0550026430E /* TKMsgManager.h in Headers */,
+				52E3A493206AAD390048C592 /* Color.h in Headers */,
+				528DA5D6205F80780063E2FC /* QQPlugin.h in Headers */,
+				52E3A464206AABFB0048C592 /* TKBaseModel.h in Headers */,
+				52E3A47E206AAD020048C592 /* TKAutoReplyCell.h in Headers */,
+				52E3A491206AAD390048C592 /* NSView+Action.h in Headers */,
+				52E3A46E206AACDF0048C592 /* TKAutoReplyWindowController.h in Headers */,
+				52E3A490206AAD390048C592 /* NSButton+Action.h in Headers */,
+				528DA5F0205F82430063E2FC /* TKQQPluginConfig.h in Headers */,
+				52E3A47F206AAD020048C592 /* TKAutoReplyContentView.h in Headers */,
+				52E3A48E206AAD390048C592 /* NSTextField+Action.h in Headers */,
+				528DA5E0205F80870063E2FC /* QQ+hook.h in Headers */,
+				529F91DD206BCF680026430E /* TKWebServerManager.h in Headers */,
+				529184C42126573C00FE4B57 /* NSDate+Action.h in Headers */,
+				528DA5FB206003CD0063E2FC /* TKHelper.h in Headers */,
+				52E3A45F206AABF00048C592 /* TKAutoReplyModel.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		528DA5D0205F80780063E2FC /* QQPlugin */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 528DA5D9205F80780063E2FC /* Build configuration list for PBXNativeTarget "QQPlugin" */;
+			buildPhases = (
+				80C03CC5B74D0C269D12D74D /* [CP] Check Pods Manifest.lock */,
+				528DA5CC205F80780063E2FC /* Sources */,
+				528DA5CD205F80780063E2FC /* Frameworks */,
+				528DA5CE205F80780063E2FC /* Headers */,
+				528DA5CF205F80780063E2FC /* Resources */,
+				528DA5EB205F80A80063E2FC /* ShellScript */,
+				63D97667867417522A277859 /* [CP] Copy Pods Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = QQPlugin;
+			productName = QQPlugin;
+			productReference = 528DA5D1205F80780063E2FC /* QQPlugin.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		528DA5C8205F80780063E2FC /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0920;
+				ORGANIZATIONNAME = TK;
+				TargetAttributes = {
+					528DA5D0205F80780063E2FC = {
+						CreatedOnToolsVersion = 9.2;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = 528DA5CB205F80780063E2FC /* Build configuration list for PBXProject "QQPlugin" */;
+			compatibilityVersion = "Xcode 8.0";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = 528DA5C7205F80780063E2FC;
+			productRefGroup = 528DA5D2205F80780063E2FC /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				528DA5D0205F80780063E2FC /* QQPlugin */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		528DA5CF205F80780063E2FC /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				52E3A46F206AACDF0048C592 /* TKAutoReplyWindowController.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		528DA5EB205F80A80063E2FC /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "#!/bin/bash\napp_name=\"QQ\"\nframework_name=\"QQPlugin\"\napp_bundle_path=\"/Applications/${app_name}.app/Contents/MacOS\"\napp_executable_path=\"${app_bundle_path}/${app_name}\"\napp_executable_backup_path=\"${app_executable_path}_backup\"\nframework_path=\"${app_bundle_path}/${framework_name}.framework\"\n# 备份WeChat原始可执行文件\nif [ ! -f \"$app_executable_backup_path\" ]\nthen\ncp \"$app_executable_path\" \"$app_executable_backup_path\"\nfi\n\nrm -rf \"./Other/Products/Debug/${framework_name}.framework\"\ncp -r \"${BUILT_PRODUCTS_DIR}/${framework_name}.framework\" \"./Other/Products/Debug/${framework_name}.framework\"\ncp -r \"${BUILT_PRODUCTS_DIR}/${framework_name}.framework\" ${app_bundle_path}\n./Other/insert_dylib --all-yes \"${framework_path}/${framework_name}\" \"$app_executable_backup_path\" \"$app_executable_path\"";
+		};
+		63D97667867417522A277859 /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Copy Pods Resources";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-QQPlugin/Pods-QQPlugin-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		80C03CC5B74D0C269D12D74D /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		528DA5CC205F80780063E2FC /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				52E3A480206AAD020048C592 /* TKAutoReplyCell.m in Sources */,
+				52E3A470206AACDF0048C592 /* TKAutoReplyWindowController.m in Sources */,
+				529184C32126573C00FE4B57 /* NSDate+Action.m in Sources */,
+				52E3A48D206AAD390048C592 /* NSButton+Action.m in Sources */,
+				52E3A47D206AAD020048C592 /* TKAutoReplyContentView.m in Sources */,
+				52E3A460206AABF00048C592 /* TKAutoReplyModel.m in Sources */,
+				528DA5EC205F81C40063E2FC /* fishhook.c in Sources */,
+				52E3A492206AAD390048C592 /* NSTextField+Action.m in Sources */,
+				529F91DC206BCF680026430E /* TKWebServerManager.m in Sources */,
+				528DA5EA205F809C0063E2FC /* main.mm in Sources */,
+				52E3A463206AABFB0048C592 /* TKBaseModel.m in Sources */,
+				529F91E1206BF0550026430E /* TKMsgManager.m in Sources */,
+				528DA5FF206003CD0063E2FC /* TKHelper.m in Sources */,
+				528DA5DF205F80870063E2FC /* QQ+hook.m in Sources */,
+				52E3A48F206AAD390048C592 /* NSView+Action.m in Sources */,
+				528DA5F1205F82430063E2FC /* TKQQPluginConfig.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		528DA5D7205F80780063E2FC /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		528DA5D8205F80780063E2FC /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+		528DA5DA205F80780063E2FC /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 39FA9A25B25855D7E65B80C4 /* Pods-QQPlugin.debug.xcconfig */;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				FRAMEWORK_VERSION = A;
+				INFOPLIST_FILE = QQPlugin/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				PRODUCT_BUNDLE_IDENTIFIER = tk.QQPlugin;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+			};
+			name = Debug;
+		};
+		528DA5DB205F80780063E2FC /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 4AB8D9FD2365E53D0213EE80 /* Pods-QQPlugin.release.xcconfig */;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				FRAMEWORK_VERSION = A;
+				INFOPLIST_FILE = QQPlugin/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				PRODUCT_BUNDLE_IDENTIFIER = tk.QQPlugin;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		528DA5CB205F80780063E2FC /* Build configuration list for PBXProject "QQPlugin" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				528DA5D7205F80780063E2FC /* Debug */,
+				528DA5D8205F80780063E2FC /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		528DA5D9205F80780063E2FC /* Build configuration list for PBXNativeTarget "QQPlugin" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				528DA5DA205F80780063E2FC /* Debug */,
+				528DA5DB205F80780063E2FC /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 528DA5C8205F80780063E2FC /* Project object */;
+}

+ 7 - 0
QQPlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:QQPlugin.xcodeproj">
+   </FileRef>
+</Workspace>

+ 5 - 0
QQPlugin.xcodeproj/xcuserdata/TK.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   type = "1"
+   version = "2.0">
+</Bucket>

+ 84 - 0
QQPlugin.xcodeproj/xcuserdata/TK.xcuserdatad/xcschemes/QQPlugin.xcscheme

@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0920"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "528DA5D0205F80780063E2FC"
+               BuildableName = "QQPlugin.framework"
+               BlueprintName = "QQPlugin"
+               ReferencedContainer = "container:QQPlugin.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <PathRunnable
+         runnableDebuggingMode = "0"
+         FilePath = "/Applications/QQ.app">
+      </PathRunnable>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "528DA5D0205F80780063E2FC"
+            BuildableName = "QQPlugin.framework"
+            BlueprintName = "QQPlugin"
+            ReferencedContainer = "container:QQPlugin.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "528DA5D0205F80780063E2FC"
+            BuildableName = "QQPlugin.framework"
+            BlueprintName = "QQPlugin"
+            ReferencedContainer = "container:QQPlugin.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 22 - 0
QQPlugin.xcodeproj/xcuserdata/TK.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>SchemeUserState</key>
+	<dict>
+		<key>QQPlugin.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>528DA5D0205F80780063E2FC</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 10 - 0
QQPlugin.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:QQPlugin.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:Pods/Pods.xcodeproj">
+   </FileRef>
+</Workspace>

+ 8 - 0
QQPlugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 23 - 0
QQPlugin.xcworkspace/xcuserdata/TK.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   type = "0"
+   version = "2.0">
+   <Breakpoints>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            shouldBeEnabled = "Yes"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "QQPlugin/Config/TKQQPluginConfig.m"
+            timestampString = "562261797.636701"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "49"
+            endingLineNumber = "49"
+            landmarkName = "-setAlfredEnable:"
+            landmarkType = "7">
+         </BreakpointContent>
+      </BreakpointProxy>
+   </Breakpoints>
+</Bucket>

+ 15 - 0
QQPlugin/Category/QQ+hook.h

@@ -0,0 +1,15 @@
+//
+//  QQ.h
+//  QQPlugin-macOS
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSObject (QQ)
+
++ (void)hookQQ;
+
+@end

+ 321 - 0
QQPlugin/Category/QQ+hook.m

@@ -0,0 +1,321 @@
+//
+//  QQ.m
+//  QQPlugin-macOS
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import "QQPlugin.h"
+#import "QQ+hook.h"
+#import "fishhook.h"
+#import "TKQQPluginConfig.h"
+#import "TKHelper.h"
+#import "TKAutoReplyWindowController.h"
+#import "TKWebServerManager.h"
+#import "TKMsgManager.h"
+
+static char tkAutoReplyWindowControllerKey;         //  自动回复窗口的关联 key
+
+@implementation  NSObject (QQ)
++ (void)hookQQ {
+    tk_hookMethod(objc_getClass("MQAIOChatViewController"), @selector(revokeMessages:), [self class], @selector(hook_revokeMessages:));
+    tk_hookMethod(objc_getClass("MsgDbService"), @selector(updateQQMessageModel:keyArray:), [self class], @selector(hook_updateMessageModel:keyArray:));
+    tk_hookMethod(objc_getClass("BHMsgManager"), @selector(appendReceiveMessageModel:msgSource:), [self class], @selector(hook_appendReceiveMessageModel:msgSource:));
+    tk_hookMethod(objc_getClass("AppController"), @selector(notifyLoginWithAccount:resultCode:userInfo:), [self class], @selector(hook_notifyLoginWithAccount:resultCode:userInfo:));
+    tk_hookMethod(objc_getClass("AppController"), @selector(notifyForceLogoutWithAccount:type:tips:), [self class], @selector(hook_notifyForceLogoutWithAccount:type:tips:));
+
+    [self setup];
+    //      替换沙盒路径
+    rebind_symbols((struct rebinding[2]) {
+        { "NSSearchPathForDirectoriesInDomains", swizzled_NSSearchPathForDirectoriesInDomains, (void *)&original_NSSearchPathForDirectoriesInDomains },
+        { "NSHomeDirectory", swizzled_NSHomeDirectory, (void *)&original_NSHomeDirectory }
+    }, 2);
+}
+
++ (void)setup {
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        [self addAssistantMenuItem];
+    });
+}
+
+- (void)hook_revokeMessages:(NSArray <BHMessageModel *>*)models {
+    if ([[TKQQPluginConfig sharedConfig] preventRevokeEnable]) return;
+    
+    [self hook_revokeMessages:models];
+}
+
+- (void)hook_updateMessageModel:(BHMessageModel *)msgModel keyArray:(id)keyArrays {
+    if (msgModel.msgType != 332 || ![[TKQQPluginConfig sharedConfig] preventRevokeEnable]) {
+        [self hook_updateMessageModel:msgModel keyArray:keyArrays];
+        return;
+    }
+    
+    NSString *revokeUserName;
+    if (IS_VALID_STRING(msgModel.groupCode)) {
+        BHGroupManager *groupManager = [objc_getClass("BHGroupManager") sharedInstance];
+        revokeUserName = [groupManager displayNameForGroupMemberWithGroupCode:msgModel.groupCode memberUin:msgModel.uin];
+    } else if (IS_VALID_STRING(msgModel.discussGroupUin)) {
+        BHGroupManager *groupManager = [objc_getClass("BHGroupManager") sharedInstance];
+        revokeUserName = [groupManager displayNameForGroupMemberWithGroupCode:msgModel.discussGroupUin memberUin:msgModel.uin];
+    } else {
+        BHFriendListManager *friendManager = [objc_getClass("BHFriendListManager") sharedInstance];
+        BHFriendModel *frindModel =  [friendManager getFriendModelByUin:msgModel.uin];
+        if (IS_VALID_STRING(frindModel.remark)) {
+            revokeUserName = frindModel.remark;
+        } else {
+            revokeUserName = frindModel.profileModel.nick;
+        }
+    }
+    
+    NSString *sessionUin = [self getUinByMessageModel:msgModel];
+    MsgDbService *msgService = [objc_getClass("MsgDbService") sharedInstance];
+    BHMessageModel *revokeMsgModel = [[msgService getMessageWithUin:[sessionUin longLongValue]
+                                                           sessType:msgModel.msgSessionType
+                                                             msgIds:@[@(msgModel.msgID)]] firstObject];
+    
+    NSString *revokeMsg = [NSString stringWithFormat:@"%@: [非文本信息]",[revokeMsgModel senderDisplayName]];
+
+    MQSessionID *sessionID = [objc_getClass("MQSessionID") sessionIdWithChatType:revokeMsgModel.chatType andUin:[revokeMsgModel.uin longLongValue]];
+    if ((revokeMsgModel != 0x0) && ([revokeMsgModel chatType] != 0x4000)) {
+        if ([revokeMsgModel msgType] != 0x4) {
+            if ([revokeMsgModel chatType] != 0x10000) {
+                revokeMsg = [(NSMutableAttributedString *)[objc_getClass("MQRecentMsgTips") tipsOfContentMsg:revokeMsgModel sessionId:sessionID]  string];
+            }
+        }
+    }
+    NSString *revokeTipContent = [NSString stringWithFormat:@"TK 拦截到一条撤回消息:\n\t%@", revokeMsg];
+    if (msgModel.isSelfSend) {
+        revokeTipContent = @"你 撤回了一条消息";
+    }
+    
+    BHTipsMsgOption *tipOpt = [[objc_getClass("BHTipsMsgOption") alloc] init];
+    tipOpt.addToDb = YES;
+
+    BHMsgManager *msgManager = [objc_getClass("BHMsgManager") sharedInstance];
+    [msgManager addTipsMessage:revokeTipContent sessType:msgModel.msgSessionType uin:sessionUin option:tipOpt];
+}
+
+- (void)hook_appendReceiveMessageModel:(NSArray *)msgModels msgSource:(long long)arg2 {
+    [self hook_appendReceiveMessageModel:msgModels msgSource:arg2];
+    
+    [msgModels enumerateObjectsUsingBlock:^(BHMessageModel *msgModel, NSUInteger idx, BOOL * _Nonnull stop) {
+        [self autoReplyWithMsg:msgModel];
+    }];
+}
+
+- (void)hook_notifyLoginWithAccount:(id)arg1 resultCode:(long long)arg2 userInfo:(id)arg3 {
+    [self hook_notifyLoginWithAccount:arg1 resultCode:arg2 userInfo:arg3];
+    
+    if ([[TKQQPluginConfig sharedConfig] alfredEnable]) {
+        [[TKWebServerManager shareManager] startServer];
+    }
+}
+
+- (void)hook_notifyForceLogoutWithAccount:(id)arg1 type:(long long)arg2 tips:(id)arg3 {
+    [[TKWebServerManager shareManager] endServer];
+    
+    [self hook_notifyForceLogoutWithAccount:arg1 type:arg2 tips:arg3];
+}
+
+#pragma mark - Other
+/**
+ 自动回复
+ 
+ @param msgModel 接收的消息
+ */
+- (void)autoReplyWithMsg:(BHMessageModel *)msgModel {
+    if (msgModel.msgType != 1024 || msgModel.isSelfSend) return;
+    
+    NSDate *now = [NSDate date];
+    NSTimeInterval nowTime = [now timeIntervalSince1970];
+    NSTimeInterval receiveTime = [msgModel time];
+    NSTimeInterval value = nowTime - receiveTime;
+    if (value > 180) { //   3 分钟前的不回复
+        return;
+    }
+    
+    NSArray *msgContentArray = [self msgContentsFromMessageModel:msgModel];
+    NSMutableString *msgContent = [NSMutableString stringWithFormat:@""];
+    if (msgContentArray.count > 0) {
+        [msgContentArray enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
+            if (IS_VALID_STRING(obj[@"text"]) && [obj[@"msg-type"] integerValue] == 0) {
+                [msgContent appendString:obj[@"text"]];
+            }
+        }];
+    }
+    
+    NSArray *autoReplyModels = [[TKQQPluginConfig sharedConfig] autoReplyModels];
+    [autoReplyModels enumerateObjectsUsingBlock:^(TKAutoReplyModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
+        if (!model.enable) return;
+        if (!model.replyContent || model.replyContent.length == 0) return;
+        if ((IS_VALID_STRING(msgModel.groupCode) || IS_VALID_STRING(msgModel.discussGroupUin)) && !model.enableGroupReply) return;
+        if (!(IS_VALID_STRING(msgModel.groupCode) || IS_VALID_STRING(msgModel.discussGroupUin)) && !model.enableSingleReply) return;
+        
+        NSArray *replyArray = [model.replyContent componentsSeparatedByString:@"|"];
+        int index = arc4random() % replyArray.count;
+        NSString *randomReplyContent = replyArray[index];
+        
+        if (model.enableRegex) {
+            NSString *regex = model.keyword;
+            NSError *error;
+            NSRegularExpression *regular = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:&error];
+            if (error) return;
+            NSInteger count = [regular numberOfMatchesInString:msgContent options:NSMatchingReportCompletion range:NSMakeRange(0, msgContent.length)];
+            if (count > 0) {
+                long long uin = [[self getUinByMessageModel:msgModel] longLongValue];
+                NSInteger delayTime = model.enableDelay ? model.delayTime : 0;
+                [self sendTextMessage:randomReplyContent uin:uin sessionType:msgModel.msgSessionType delay:delayTime];
+            }
+        } else {
+            NSArray * keyWordArray = [model.keyword componentsSeparatedByString:@"|"];
+            [keyWordArray enumerateObjectsUsingBlock:^(NSString *keyword, NSUInteger idx, BOOL * _Nonnull stop) {
+                if ([keyword isEqualToString:@"*"] || [msgContent isEqualToString:keyword]) {
+                    long long uin = [[self getUinByMessageModel:msgModel] longLongValue];
+                    NSInteger delayTime = model.enableDelay ? model.delayTime : 0;
+                    [self sendTextMessage:randomReplyContent uin:uin sessionType:msgModel.msgSessionType delay:delayTime];
+                }
+            }];
+        }
+    }];
+}
+
+- (void)sendTextMessage:(NSString *)msg uin:(long long)uin sessionType:(int)type delay:(NSInteger)delayTime {
+    if (delayTime == 0) {
+        [TKMsgManager sendTextMessage:msg
+                                  uin:uin
+                          sessionType:type];
+        return;
+    }
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [TKMsgManager sendTextMessage:msg
+                                      uin:uin
+                              sessionType:type];
+        });
+    });
+}
+/**
+ 获取当前消息的 uin
+
+ @param msgModel 消息model
+ @return 消息的 uin
+ */
+- (NSString *)getUinByMessageModel:(BHMessageModel *)msgModel {
+    NSString *currentUin;
+    if (IS_VALID_STRING(msgModel.groupCode)) {
+        currentUin = msgModel.groupCode;
+    } else if (IS_VALID_STRING(msgModel.discussGroupUin)) {
+        currentUin = msgModel.discussGroupUin;
+    } else {
+        currentUin = msgModel.uin;
+    }
+    return currentUin;
+}
+
+/**
+ 获取当前消息的内容数组
+
+ @param model 消息model
+ @return 内容数组
+ */
+- (NSArray *)msgContentsFromMessageModel:(BHMessageModel *)model {
+    NSData *jsonData = [model.smallContent dataUsingEncoding:NSUTF8StringEncoding];
+    NSError *error;
+    NSArray *msgContent = [NSJSONSerialization JSONObjectWithData:jsonData
+                                                          options:NSJSONReadingMutableContainers
+                                                            error:&error];
+    
+    return error ? nil : msgContent;
+}
+
+#pragma mark - 菜单栏初始化
+/**
+ 菜单栏添加 menuItem
+ */
++ (void)addAssistantMenuItem {
+    //        消息防撤回
+    NSMenuItem *preventRevokeItem = [[NSMenuItem alloc] initWithTitle:@"开启消息防撤回" action:@selector(onPreventRevoke:) keyEquivalent:@"T"];
+    preventRevokeItem.state = [[TKQQPluginConfig sharedConfig] preventRevokeEnable];
+    
+    //        自动回复
+    NSMenuItem *autoReplyItem = [[NSMenuItem alloc] initWithTitle:@"自动回复设置" action:@selector(onAutoReply:) keyEquivalent:@"K"];
+    
+    //        开启 alfred
+    NSMenuItem *enableAlfredItem = [[NSMenuItem alloc] initWithTitle:@"开启 alfred" action:@selector(onEnableAlfred:) keyEquivalent:@""];
+    enableAlfredItem.state = [[TKQQPluginConfig sharedConfig] alfredEnable];
+    
+    NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@"QQ小助手"];
+    [subMenu addItem:preventRevokeItem];
+    [subMenu addItem:autoReplyItem];
+    [subMenu addItem:enableAlfredItem];
+    
+    NSMenuItem *menuItem = [[NSMenuItem alloc] init];
+    [menuItem setTitle:@"QQ小助手"];
+    [menuItem setSubmenu:subMenu];
+    
+    [[[NSApplication sharedApplication] mainMenu] addItem:menuItem];
+}
+
+/**
+ 菜单栏-QQ小助手-消息防撤回 设置
+ 
+ @param item 消息防撤回的item
+ */
+- (void)onPreventRevoke:(NSMenuItem *)item {
+    item.state = !item.state;
+    [[TKQQPluginConfig sharedConfig] setPreventRevokeEnable:item.state];
+}
+
+/**
+ 菜单栏-QQ小助手-自动回复 设置
+ 
+ @param item 自动回复设置的item
+ */
+- (void)onAutoReply:(NSMenuItem *)item {
+    MainMenuController *mainMenu = [objc_getClass("MainMenuController") sharedInstance];
+    TKAutoReplyWindowController *autoReplyWC = objc_getAssociatedObject(mainMenu, &tkAutoReplyWindowControllerKey);
+    
+    if (!autoReplyWC) {
+        autoReplyWC = [[TKAutoReplyWindowController alloc] initWithWindowNibName:@"TKAutoReplyWindowController"];
+        objc_setAssociatedObject(mainMenu, &tkAutoReplyWindowControllerKey, autoReplyWC, OBJC_ASSOCIATION_RETAIN);
+    }
+    
+    [autoReplyWC showWindow:autoReplyWC];
+    [autoReplyWC.window center];
+    [autoReplyWC.window makeKeyWindow];
+}
+
+- (void)onEnableAlfred:(NSMenuItem *)item {
+    item.state = !item.state;
+    if (item.state) {
+        [[TKWebServerManager shareManager] startServer];
+    } else {
+        [[TKWebServerManager shareManager] endServer];
+    }
+    [[TKQQPluginConfig sharedConfig] setAlfredEnable:item.state];
+}
+
+#pragma mark - 替换 NSSearchPathForDirectoriesInDomains & NSHomeDirectory
+static NSArray<NSString *> *(*original_NSSearchPathForDirectoriesInDomains)(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
+NSArray<NSString *> *swizzled_NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde) {
+    NSMutableArray<NSString *> *paths = [original_NSSearchPathForDirectoriesInDomains(directory, domainMask, expandTilde) mutableCopy];
+    NSString *sandBoxPath = [NSString stringWithFormat:@"%@/Library/Containers/com.tencent.qq/Data",original_NSHomeDirectory()];
+    [paths enumerateObjectsUsingBlock:^(NSString *filePath, NSUInteger idx, BOOL * _Nonnull stop) {
+        NSRange range = [filePath rangeOfString:original_NSHomeDirectory()];
+        if (range.length > 0) {
+            NSMutableString *newFilePath = [filePath mutableCopy];
+            [newFilePath replaceCharactersInRange:range withString:sandBoxPath];
+            paths[idx] = newFilePath;
+        }
+    }];
+    return paths;
+}
+
+static NSString *(*original_NSHomeDirectory)(void);
+NSString *swizzled_NSHomeDirectory(void) {
+    return [NSString stringWithFormat:@"%@/Library/Containers/com.tencent.qq/Data",original_NSHomeDirectory()];
+}
+
+@end
+

+ 15 - 0
QQPlugin/Common/Category/NSButton+Action.h

@@ -0,0 +1,15 @@
+//
+//  NSButton+Action.h
+//  QQPlugin
+//
+//  Created by TK on 2017/9/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSButton (Action)
++ (instancetype)tk_buttonWithTitle:(NSString *)title target:(id)target action:(SEL)action;
++ (instancetype)tk_checkboxWithTitle:(NSString *)title target:(id)target action:(SEL)action;
+
+@end

+ 33 - 0
QQPlugin/Common/Category/NSButton+Action.m

@@ -0,0 +1,33 @@
+//
+//  NSButton+Action.m
+//  QQPlugin
+//
+//  Created by TK on 2017/9/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "NSButton+Action.h"
+
+@implementation NSButton (Action)
+
++ (instancetype)tk_checkboxWithTitle:(NSString *)title target:(id)target action:(SEL)action {
+    NSButton *btn = [NSButton tk_buttonWithTitle:title target:target action:action];
+    [btn setButtonType:NSButtonTypeSwitch];
+    
+    return btn;
+}
+
++ (instancetype)tk_buttonWithTitle:(NSString *)title target:(id)target action:(SEL)action {
+    NSButton *btn = ({
+        NSButton *btn = [[NSButton alloc] init];
+        btn.title = title;
+        btn.target = target;
+        btn.action = action;
+        
+        btn;
+    });
+    
+    return btn;
+}
+
+@end

+ 16 - 0
QQPlugin/Common/Category/NSDate+Action.h

@@ -0,0 +1,16 @@
+//
+//  NSDate+Action.h
+//  WeChatPlugin
+//
+//  Created by TK on 2018/7/25.
+//  Copyright © 2018年 tk. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSDate (Action)
+
+- (BOOL)isToday;
+- (BOOL)isYesterday;
+
+@end

+ 35 - 0
QQPlugin/Common/Category/NSDate+Action.m

@@ -0,0 +1,35 @@
+//
+//  NSDate+Action.m
+//  WeChatPlugin
+//
+//  Created by TK on 2018/7/25.
+//  Copyright © 2018年 tk. All rights reserved.
+//
+
+#import "NSDate+Action.h"
+
+@implementation NSDate (Action)
+
+- (BOOL)isToday {
+    NSCalendar *calendar = [NSCalendar currentCalendar];
+    NSInteger unit = NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear ;
+    
+    NSDateComponents *nowComponents = [calendar components:unit fromDate:[NSDate date]];
+    NSDateComponents *selfComponents = [calendar components:unit fromDate:self];
+    
+    return (selfComponents.year == nowComponents.year) && (selfComponents.month == nowComponents.month) && (selfComponents.day == nowComponents.day);
+}
+
+- (BOOL)isYesterday {
+    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+    formatter.dateFormat = @"yyyy-MM-dd";
+    
+    NSDate *nowDate = [formatter dateFromString:[formatter stringFromDate:[NSDate date]]];
+    NSDate *selfDate = [formatter dateFromString:[formatter stringFromDate:self]];;
+    NSCalendar *calendar = [NSCalendar currentCalendar];
+    NSDateComponents *cmps = [calendar components:NSCalendarUnitDay fromDate:selfDate toDate:nowDate options:0];
+    
+    return cmps.day == 1;
+}
+
+@end

+ 15 - 0
QQPlugin/Common/Category/NSTextField+Action.h

@@ -0,0 +1,15 @@
+//
+//  NSTextField+Action.h
+//  QQPlugin
+//
+//  Created by TK on 2017/9/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSTextField (Action)
+
++ (instancetype)tk_labelWithString:(NSString *)stringValue;
+
+@end

+ 28 - 0
QQPlugin/Common/Category/NSTextField+Action.m

@@ -0,0 +1,28 @@
+//
+//  NSTextField+Action.m
+//  QQPlugin
+//
+//  Created by TK on 2017/9/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "NSTextField+Action.h"
+
+@implementation NSTextField (Action)
+
++ (instancetype)tk_labelWithString:(NSString *)stringValue {
+    NSTextField *textField = ({
+        NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 200, 17)];
+        [textField setStringValue:stringValue];
+        [textField setBezeled:NO];
+        [textField setDrawsBackground:NO];
+        [textField setEditable:NO];
+        [textField setSelectable:NO];
+        
+        textField;
+    });
+
+    return textField;
+}
+
+@end

+ 15 - 0
QQPlugin/Common/Category/NSView+Action.h

@@ -0,0 +1,15 @@
+//
+//  NSView+Action.h
+//  QQPlugin
+//
+//  Created by TK on 2017/8/20.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSView (Action)
+
+- (void)addSubviews:(NSArray *)subViews;
+
+@end

+ 20 - 0
QQPlugin/Common/Category/NSView+Action.m

@@ -0,0 +1,20 @@
+//
+//  NSView+Action.m
+//  QQPlugin
+//
+//  Created by TK on 2017/8/20.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "NSView+Action.h"
+
+@implementation NSView (Action)
+
+- (void)addSubviews:(NSArray *)subViews {
+    for (NSView *v in subViews) {
+        NSAssert([v isKindOfClass:[NSView class]], @"the elements must be a view!");
+        [self addSubview:v];
+    }
+}
+
+@end

+ 26 - 0
QQPlugin/Common/Color.h

@@ -0,0 +1,26 @@
+//
+//  Color.h
+//  QQPlugin
+//
+//  Created by TK on 2017/8/20.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#ifndef Color_h
+#define Color_h
+
+#define TK_RGBA(r, g, b, a) [NSColor colorWithRed:(r) / 255.0 \
+green:(g) / 255.0 \
+blue:(b) / 255.0 \
+alpha:(a)]
+
+#define TK_RGB(r, g, b) TK_RGBA(r, g, b, 1.0)
+#define TK_GRAYA(c, a) TK_RGBA(c, c, c, a)
+#define TK_GRAY(c) TK_GRAYA(c, 1.0)
+
+#define kBG1 TK_GRAY(0xec)
+#define kBG2 TK_GRAY(0xe3)
+#define kBG3 TK_GRAYA(0x2a, 0.5)
+#define kBG4 TK_GRAYA(0x7a, 0.5)
+
+#endif /* Color_h */

+ 20 - 0
QQPlugin/Config/TKQQPluginConfig.h

@@ -0,0 +1,20 @@
+//
+//  TKQQPluginConfig.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/19.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TKQQPluginConfig : NSObject
+
+@property (nonatomic, assign) BOOL preventRevokeEnable;                 /**<    是否开启防撤回    */
+@property (nonatomic, assign) BOOL alfredEnable;                        /**<    是否开启Alfred   */  
+@property (nonatomic, copy) NSMutableArray *autoReplyModels;            /**<    自动回复的数组    */
+
++ (instancetype)sharedConfig;
+- (void)saveAutoReplyModels;
+
+@end

+ 118 - 0
QQPlugin/Config/TKQQPluginConfig.m

@@ -0,0 +1,118 @@
+//
+//  TKQQPluginConfig.m
+//  QQPlugin
+//
+//  Created by TK on 2018/3/19.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import "TKQQPluginConfig.h"
+#import "TKHelper.h"
+#import "TKAutoReplyModel.h"
+
+static NSString * const kTKPreventRevokeEnableKey = @"kTKPreventRevokeEnableKey";
+static NSString * const kTKAlfredEnableKey = @"kTKAlfredEnableKey";
+static NSString * const kTKQQResourcesPath = @"/Applications/QQ.app/Contents/MacOS/QQPlugin.framework/Resources/";
+
+@interface TKQQPluginConfig ()
+@property (nonatomic, copy) NSString *autoReplyPlistFilePath;
+@end
+
+@implementation TKQQPluginConfig
+
++ (instancetype)sharedConfig {
+    static TKQQPluginConfig *config = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        config = [[TKQQPluginConfig alloc] init];
+    });
+    return config;
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        _preventRevokeEnable = [[NSUserDefaults standardUserDefaults] boolForKey:kTKPreventRevokeEnableKey];
+        _alfredEnable = [[NSUserDefaults standardUserDefaults] boolForKey:kTKAlfredEnableKey];
+    }
+    return self;
+}
+
+- (void)setPreventRevokeEnable:(BOOL)preventRevokeEnable {
+    _preventRevokeEnable = preventRevokeEnable;
+    [[NSUserDefaults standardUserDefaults] setBool:preventRevokeEnable forKey:kTKPreventRevokeEnableKey];
+    [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (void)setAlfredEnable:(BOOL)alfredEnable {
+    _alfredEnable = alfredEnable;
+    [[NSUserDefaults standardUserDefaults] setBool:alfredEnable forKey:kTKAlfredEnableKey];
+    [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+#pragma mark - 自动回复
+- (NSArray *)autoReplyModels {
+    if (!_autoReplyModels) {
+        _autoReplyModels = [self getModelsWithClass:[TKAutoReplyModel class] filePath:self.autoReplyPlistFilePath];
+    }
+    return _autoReplyModels;
+}
+
+- (void)saveAutoReplyModels {
+    NSMutableArray *needSaveModels = [NSMutableArray array];
+    [_autoReplyModels enumerateObjectsUsingBlock:^(TKAutoReplyModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
+        if (model.hasEmptyKeywordOrReplyContent) {
+            model.enable = NO;
+            model.enableGroupReply = NO;
+        }
+        model.replyContent = model.replyContent == nil ? @"" : model.replyContent;
+        model.keyword = model.keyword == nil ? @"" : model.keyword;
+        [needSaveModels addObject:model.dictionary];
+    }];
+    [needSaveModels writeToFile:self.autoReplyPlistFilePath atomically:YES];
+}
+
+
+- (NSString *)autoReplyPlistFilePath {
+    if (!_autoReplyPlistFilePath) {
+        _autoReplyPlistFilePath = [self getSandboxFilePathWithPlistName:@"TKAutoReplyModels.plist"];
+    }
+    return _autoReplyPlistFilePath;
+}
+
+#pragma mark - common
+- (NSMutableArray *)getModelsWithClass:(Class)class filePath:(NSString *)filePath {
+    NSArray *originModels = [NSArray arrayWithContentsOfFile:filePath];
+    NSMutableArray *newModels = [NSMutableArray array];
+    
+    __weak Class weakClass = class;
+    [originModels enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        TKAutoReplyModel *model = [[weakClass alloc] initWithDict:obj];
+        [newModels addObject:model];
+    }];
+    return newModels;
+}
+
+- (NSString *)getSandboxFilePathWithPlistName:(NSString *)plistName {
+    NSFileManager *manager = [NSFileManager defaultManager];
+    NSString *QQPluginDirectory = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TKQQPlugin"];
+    NSString *plistFilePath = [QQPluginDirectory stringByAppendingPathComponent:plistName];
+    if ([manager fileExistsAtPath:plistFilePath]) {
+        return plistFilePath;
+    }
+    
+    [manager createDirectoryAtPath:QQPluginDirectory withIntermediateDirectories:YES attributes:nil error:nil];
+    NSString *resourcesFilePath = [kTKQQResourcesPath stringByAppendingString:plistName];
+    if (![manager fileExistsAtPath:resourcesFilePath]) {
+        return plistFilePath;
+    }
+    
+    NSError *error = nil;
+    [manager copyItemAtPath:resourcesFilePath toPath:plistFilePath error:&error];
+    if (!error) {
+        return plistFilePath;
+    }
+    return resourcesFilePath;
+}
+
+@end

+ 26 - 0
QQPlugin/Info.plist

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.2</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2018年 TK. All rights reserved.</string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>

+ 24 - 0
QQPlugin/Model/TKAutoReplyModel.h

@@ -0,0 +1,24 @@
+//
+//  TKAutoReplyModel.h
+//  QQPlugin
+//
+//  Created by TK on 2017/8/18.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKBaseModel.h"
+
+@interface TKAutoReplyModel : TKBaseModel
+
+@property (nonatomic, assign) BOOL enable;                  /**<    是否开启自动回复     */
+@property (nonatomic, copy) NSString *keyword;              /**<    自动回复关键字       */
+@property (nonatomic, copy) NSString *replyContent;         /**<    自动回复的内容       */
+@property (nonatomic, assign) BOOL enableGroupReply;        /**<    是否开启群聊自动回复  */
+@property (nonatomic, assign) BOOL enableSingleReply;       /**<    是否开启私聊自动回复  */
+@property (nonatomic, assign) BOOL enableRegex;             /**<    是否开启正则匹配     */
+@property (nonatomic, assign) BOOL enableDelay;             /**<    是否开启延迟回复     */
+@property (nonatomic, assign) NSInteger delayTime;          /**<    延迟时间            */
+
+- (BOOL)hasEmptyKeywordOrReplyContent;
+
+@end

+ 43 - 0
QQPlugin/Model/TKAutoReplyModel.m

@@ -0,0 +1,43 @@
+//
+//  TKAutoReplyModel.m
+//  QQPlugin
+//
+//  Created by TK on 2017/8/18.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKAutoReplyModel.h"
+
+@implementation TKAutoReplyModel
+
+- (instancetype)initWithDict:(NSDictionary *)dict {
+    self = [super init];
+    if (self) {
+        self.enable = [dict[@"enable"] boolValue];
+        self.keyword = dict[@"keyword"];
+        self.replyContent = dict[@"replyContent"];
+        self.enableGroupReply = [dict[@"enableGroupReply"] boolValue];
+        self.enableSingleReply = [dict[@"enableSingleReply"] boolValue];
+        self.enableRegex = [dict[@"enableRegex"] boolValue];
+        self.enableDelay = [dict[@"enableDelay"] boolValue];
+        self.delayTime = [dict[@"delayTime"] floatValue];
+    }
+    return self;
+}
+
+- (NSDictionary *)dictionary {
+    return @{@"enable": @(self.enable),
+             @"keyword": self.keyword,
+             @"replyContent": self.replyContent,
+             @"enableGroupReply": @(self.enableGroupReply),
+             @"enableSingleReply": @(self.enableSingleReply),
+             @"enableRegex": @(self.enableRegex),
+             @"enableDelay": @(self.enableDelay),
+             @"delayTime":@(self.delayTime)};
+}
+
+- (BOOL)hasEmptyKeywordOrReplyContent {
+    return (self.keyword == nil || self.replyContent == nil || [self.keyword isEqualToString:@""] || [self.replyContent isEqualToString:@""]);
+}
+
+@end

+ 16 - 0
QQPlugin/Model/TKBaseModel.h

@@ -0,0 +1,16 @@
+//
+//  TKBaseModel.h
+//  QQPlugin
+//
+//  Created by TK on 2017/9/17.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TKBaseModel : NSObject
+
+- (instancetype)initWithDict:(NSDictionary *)dict;
+- (NSDictionary *)dictionary;
+
+@end

+ 22 - 0
QQPlugin/Model/TKBaseModel.m

@@ -0,0 +1,22 @@
+//
+//  TKBaseModel.m
+//  QQPlugin
+//
+//  Created by TK on 2017/9/17.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKBaseModel.h"
+
+@implementation TKBaseModel
+
+- (instancetype)initWithDict:(NSDictionary *)dict {
+    NSAssert(NO, @"the mothed initWithDict: must be override by subclass");
+    return nil;
+}
+
+- (NSDictionary *)dictionary {
+    NSAssert(NO, @"the mothed dictionary must be override by subclass");
+    return nil;
+}
+@end

+ 245 - 0
QQPlugin/QQPlugin.h

@@ -0,0 +1,245 @@
+//
+//  QQPlugin.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "NSView+Action.h"
+#import "NSButton+Action.h"
+#import "NSTextField+Action.h"
+#import "NSDate+Action.h"
+#import "Color.h"
+#import <objc/runtime.h>
+
+#define IS_VALID_STRING(STR)          ((STR) && ![(STR) isEqualToString:@""])
+
+struct _BHMessageSession {
+    int _field1;
+    unsigned long long _field2;
+    unsigned long long _field3;
+    unsigned long long _field4;
+};
+
+@class BHFriendListManager, DiscussGroupInfo, BHGroupModel;
+
+#pragma mark - Model
+
+@interface MQBaseElem : NSObject
+{
+    NSString *_uinStr;
+}
+@end
+
+@interface Buddy : MQBaseElem
+{
+    BHFriendListManager *_friendListManager;
+}
+@end
+
+@interface Discuss : MQBaseElem
+@property(retain, nonatomic) DiscussGroupInfo *discussInfo;
+@property(nonatomic) unsigned long long discussSearchType;
+- (id)combinDiscussName;
+@end
+
+@interface Group : MQBaseElem
+@property(retain, nonatomic) BHGroupModel *troopModel;
+@end
+
+@interface DiscussGroupInfo : NSObject
+@property(retain, nonatomic) NSString *name;
+@property(nonatomic) int flag;
+@property(nonatomic) int memberNum;
+@property(nonatomic) long long groupUin;
+@end
+
+@interface DiscussMemberInfo : NSObject
+@property(retain, nonatomic) NSString *remarkName;
+@end
+
+@interface BHFriendGroupModel : NSObject
+@property(copy, nonatomic) NSString *groupName; 
+@end
+
+@interface BHGroupModel : NSObject
+@property(readonly, nonatomic) NSString *groupCode;
+@property(readonly, nonatomic) NSString *displayName;
+@property(copy, nonatomic) NSString *groupName;
+@property(copy, nonatomic) NSString *groupRemark;
+@property(nonatomic) unsigned long long groupMemberCount;
+@end
+
+@interface BHFontInfo : NSObject
+@end
+
+@interface BHMessageModel : NSObject
+@property(nonatomic) unsigned int time;
+@property(nonatomic) int msgID; 
+@property(retain, nonatomic) NSString *groupCode;
+@property(retain, nonatomic) NSString *discussGroupUin;
+@property(readonly) BOOL isSelfSend;
+@property(nonatomic) int msgSessionType;
+@property(nonatomic) int msgType;
+@property(retain, nonatomic) NSString *nickname;
+@property(copy, nonatomic) NSString *smallContent;
+@property(retain, nonatomic) NSString *uin;
+@property(readonly) NSString *senderUin;
+@property(copy, nonatomic) NSString *summaryTextContent;
+@property(readonly, nonatomic) NSArray *contentPartArray;
+- (id)senderDisplayName;
+- (int)chatType;
+@end
+
+@interface BHProfileModel : NSObject
+@property(copy, nonatomic) NSString *nick;
+@property(readonly) NSString *displayName;
+@property(copy, nonatomic) NSString *uin;
+@end
+
+@interface BHFriendModel : NSObject
+@property(copy, nonatomic) NSString *remark;
+@property(copy, nonatomic) NSString *uin;
+@property(retain, nonatomic) BHProfileModel *profileModel;
+@property(copy, nonatomic) NSString *groupID;
+@property(copy, nonatomic) NSString *showName;
+@end
+
+@interface BHTipsMsgOption : NSObject
+@property(nonatomic) BOOL addToDb;
+@end
+
+#pragma mark - ViewController
+@interface MQAIOChatViewController : NSObject
+- (void)revokeMessages:(id)arg1;
+@end
+
+#pragma mark - Controller
+@interface MainMenuController : NSObject
++ (id)sharedInstance;
+@end
+
+@interface AppController : NSObject
+- (void)notifyForceLogoutWithAccount:(id)arg1 type:(long long)arg2 tips:(id)arg3;
+- (void)notifyLoginWithAccount:(id)arg1 resultCode:(long long)arg2 userInfo:(id)arg3;
+@end
+
+#pragma mark - Manager
+@interface QQBaseSingleton : NSObject <NSCopying>
++ (id)sharedInstance;
+@end
+
+@interface BHMsgManager : NSObject
++ (id)sharedInstance;
+- (id)defaultFontInfo;
+- (void)appendReceiveMessageModel:(id)arg1 msgSource:(long long)arg2;
+- (void)addTipsMessage:(id)arg1 sessType:(int)arg2 uin:(id)arg3 option:(id)arg4;
+- (id)sendMessagePacket:(id)arg1 target:(struct _BHMessageSession)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+- (id)getImagePathByMsg:(id)arg1 imageSize:(long long)arg2;
+- (id)getShortVideoPathByMsg:(id)arg1;
+- (void)downloadImageByMsg:(id)arg1 content:(id)arg2 completion:(id)arg3 ProgressBlock:(id)arg4;
+@end
+
+@interface MQAIOManager : NSObject
++ (id)sharedInstance;
+- (void)showAIOOfUin:(unsigned long long)arg1 chatType:(int)arg2 bringToTop:(BOOL)arg3;
+@end
+
+@interface BHAvatarManager : NSObject
++ (id)sharedInstance;
+- (id)userAvatarPathWithUIN:(id)arg1;
+- (id)groupAvatarPathWithGroupCode:(id)arg1;
+@end
+
+@interface BHDiscussGroupManager : NSObject
++ (id)sharedInstance;
+- (id)getDiscussMember:(id)arg1 memberUin:(long long)arg2;
+- (id)getGroupInfo:(long long)arg1;
+@end
+
+@interface BHFriendListManager : NSObject
+{
+    NSMutableDictionary *_friendCache;
+    NSMutableDictionary *_groupCache;
+}
+- (id)getFriendModelByUin:(id)arg1;
+@end
+
+@interface BHGroupManager : NSObject
+- (id)displayNameForGroupMemberWithGroupCode:(id)arg1 memberUin:(id)arg2;
+@end
+
+#pragma mark - Server
+@interface MsgDbService : NSObject
+- (void)updateQQMessageModel:(id)arg1 keyArray:(id)arg2;
+- (id)getMessageWithUin:(long long)arg1 sessType:(int)arg2 msgIds:(id)arg3;
+@end
+
+#pragma mark - Other
+@interface ContactSearcherInter : NSObject
+@property(retain, nonatomic) NSMutableArray <Buddy *> *searchedBuddys;
+@property(retain, nonatomic) NSMutableArray <Discuss *> *searchedDiscusses;
+@property(retain, nonatomic) NSMutableArray <Group *> *searchedGroups;
+- (void)Query:(id)arg1;
+@end
+
+@interface BHCompoundMessagePacket : NSObject
+@property(readonly, nonatomic) int msgType;
+@property(retain, nonatomic) BHFontInfo *fontInfo;
+- (void)addText:(id)arg1;
+- (id)initWithMessageType:(int)arg1;
+@end
+
+@interface MQRecentSessionManager : NSObject
++ (id)sharedLogicEngine;
+- (id)getSessionIDList;
+@end
+
+@interface GroupFolderManager : NSObject
++ (id)sharedFolderManager;
+- (id)BHGroupModelWithUin:(id)arg1;
+@end
+
+@interface BHProfileManager : NSObject
++ (id)sharedInstance;
+- (id)getProfileWithUIN:(id)arg1;
+@end
+
+@interface MQSessionID : NSObject <NSCopying>
++ (id)sessionIdWithChatType:(int)arg1 andUin:(unsigned long long)arg2;
+@property(readonly, nonatomic) int chatType; // @synthesize
+@property(readonly, nonatomic) unsigned long long uin; // @synthesize uin=_uin;
+- (id)uinString;
+@end
+
+@interface UnreadMsgMgr : NSObject
++ (id)sharedUnreadMsgMgr;
+- (id)getRecentModelFromSessionID:(id)arg1;
+@end
+
+@interface MQRecentMsgTips : NSObject
++ (id)tipsOfContentMsg:(id)arg1 sessionId:(id)arg2;
+@end
+
+@interface TXImageUtils : NSObject
++ (id)imageOfSession:(id)arg1;
+@end
+
+
+@interface TChatHistoryMsgManager : QQBaseSingleton
+- (id)getHistoryMsgModel:(long long)arg1 sessType:(int)arg2 filter:(unsigned long long)arg3;
+@end
+
+
+@interface TChatHistoryMsgModelWrapper : NSObject
+- (void)_loadMoreMessageUpForAllMsgType:(void(^)(void))arg1;
+@property(readonly, nonatomic) NSArray *msgArray;
+@end
+
+@interface VideoMsgLoadManager : NSObject
++ (void)requsetVideoMsgVideo:(id)arg1 completion:(id)arg2;
+@end
+
+

+ 34 - 0
QQPlugin/Utils/TKHelper.h

@@ -0,0 +1,34 @@
+//
+//  TKHelper.h
+//  QQPlugin
+//
+//  Created by TK on 2017/4/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <objc/runtime.h>
+
+@interface TKHelper : NSObject
+
+/**
+ 替换对象方法
+
+ @param originalClass 原始类
+ @param originalSelector 原始类的方法
+ @param swizzledClass 替换类
+ @param swizzledSelector 替换类的方法
+ */
+void tk_hookMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector);
+
+/**
+ 替换类方法
+ 
+ @param originalClass 原始类
+ @param originalSelector 原始类的类方法
+ @param swizzledClass 替换类
+ @param swizzledSelector 替换类的类方法
+ */
+void tk_hookClassMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector);
+
+@end

+ 45 - 0
QQPlugin/Utils/TKHelper.m

@@ -0,0 +1,45 @@
+//
+//  TKHelper.m
+//  QQPlugin
+//
+//  Created by TK on 2017/4/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKHelper.h"
+
+@implementation TKHelper
+
+/**
+ 替换对象方法
+ 
+ @param originalClass 原始类
+ @param originalSelector 原始类的方法
+ @param swizzledClass 替换类
+ @param swizzledSelector 替换类的方法
+ */
+void tk_hookMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector) {
+    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
+    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
+    if(originalMethod && swizzledMethod) {
+        method_exchangeImplementations(originalMethod, swizzledMethod);
+    }
+}
+
+/**
+ 替换类方法
+ 
+ @param originalClass 原始类
+ @param originalSelector 原始类的类方法
+ @param swizzledClass 替换类
+ @param swizzledSelector 替换类的类方法
+ */
+void tk_hookClassMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector) {
+    Method originalMethod = class_getClassMethod(originalClass, originalSelector);
+    Method swizzledMethod = class_getClassMethod(swizzledClass, swizzledSelector);
+    if(originalMethod && swizzledMethod) {
+        method_exchangeImplementations(originalMethod, swizzledMethod);
+    }
+}
+
+@end

+ 15 - 0
QQPlugin/Utils/TKMsgManager.h

@@ -0,0 +1,15 @@
+//
+//  TKMsgManager.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/31.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TKMsgManager : NSObject
+
++ (void)sendTextMessage:(NSString *)msg uin:(long long)uin sessionType:(int)type;
+
+@end

+ 37 - 0
QQPlugin/Utils/TKMsgManager.m

@@ -0,0 +1,37 @@
+//
+//  TKMsgManager.m
+//  QQPlugin
+//
+//  Created by TK on 2018/3/31.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import "TKMsgManager.h"
+#import "QQPlugin.h"
+
+@implementation TKMsgManager
+
++ (void)sendTextMessage:(NSString *)msg uin:(long long)uin sessionType:(int)type {
+    BHCompoundMessagePacket *packet =  [[objc_getClass("BHCompoundMessagePacket") alloc] initWithMessageType:1024];
+    [packet setValue:@[@{@"msg-type":@(0), @"text":msg}] forKey:@"array"];
+    struct _BHMessageSession session = {0,0,0,0};
+    session._field1 = type;
+    switch (type) {
+        case 1:
+            session._field2 = uin;
+            break;
+        case 101:
+            session._field3 = uin;
+            break;
+        case 201:
+            session._field4 = uin;
+            break;
+        default:
+            break;
+    }
+    BHMsgManager *manager = [objc_getClass("BHMsgManager") sharedInstance];
+    packet.fontInfo  = [manager defaultFontInfo];
+    [manager sendMessagePacket:packet target:session completion:nil ProgressBlock:nil];
+}
+
+@end

+ 18 - 0
QQPlugin/Utils/TKWebServerManager.h

@@ -0,0 +1,18 @@
+//
+//  TKWebServerManager.h
+//  QQPlugin
+//
+//  Created by TK on 2018/3/24.
+//  Copyright © 2018年 tk. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TKWebServerManager : NSObject
+
++ (instancetype)shareManager;
+
+- (void)startServer;
+- (void)endServer;
+
+@end

+ 479 - 0
QQPlugin/Utils/TKWebServerManager.m

@@ -0,0 +1,479 @@
+//
+//  TKWebServerManager.m
+//  QQPlugin
+//
+//  Created by TK on 2018/3/24.
+//  Copyright © 2018年 tk. All rights reserved.
+//
+
+#import "TKWebServerManager.h"
+#import "QQPlugin.h"
+#import <GCDWebServer.h>
+#import <GCDWebServerDataResponse.h>
+#import <GCDWebServerURLEncodedFormRequest.h>
+#import "TKMsgManager.h"
+
+
+@interface TKWebServerManager ()
+@property (nonatomic, strong) GCDWebServer *webServer;
+@end
+
+@implementation TKWebServerManager
+static int port=52777;
+
++ (instancetype)shareManager {
+    static TKWebServerManager *manager = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        manager = [[TKWebServerManager alloc] init];
+    });
+    return manager;
+}
+
+- (void)startServer {
+    if (self.webServer) return;
+    
+    NSDictionary *options = @{GCDWebServerOption_Port: [NSNumber numberWithInt:port],
+                              GCDWebServerOption_BindToLocalhost: @YES,
+                              GCDWebServerOption_ConnectedStateCoalescingInterval: @2,
+                              };
+    
+    self.webServer = [[GCDWebServer alloc] init];
+    [self addHandleForSearchUser];
+    [self addHandleForOpenSession];
+    [self addHandleForSearchUserChatLog];
+    [self addHandleForSendMsg];
+    [self.webServer startWithOptions:options error:nil];
+}
+
+- (void)endServer {
+    if( [self.webServer isRunning] ) {
+        [self.webServer stop];
+        [self.webServer removeAllHandlers];
+        self.webServer = nil;
+    }
+}
+
+- (void)addHandleForSearchUser {
+    __weak typeof(self) weakSelf = self;
+    
+    [self.webServer addHandlerForMethod:@"GET" path:@"/QQ-plugin/user" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
+        
+        if (![weakSelf isLocalhost:request.headers[@"Host"]]) {
+            return [GCDWebServerResponse responseWithStatusCode:404];
+        }
+        
+        NSString *keyword = request.query ? request.query[@"keyword"] ? request.query[@"keyword"] : @"" : @"";
+        NSMutableArray *sessionList = [NSMutableArray array];
+
+        if ([keyword isEqualToString:@""]) {
+            sessionList = [weakSelf getRecentSessionList];
+            return [GCDWebServerDataResponse responseWithJSONObject:sessionList];
+        }
+        
+        ContactSearcherInter *inter = [[objc_getClass("ContactSearcherInter") alloc] init];
+        [inter Query:keyword];
+    
+        [inter.searchedBuddys enumerateObjectsUsingBlock:^(Buddy * buddy, NSUInteger idx, BOOL * _Nonnull stop) {
+            [sessionList addObject:[weakSelf dictFromBuddySearchResult:buddy]];
+        }];
+        
+        [inter.searchedDiscusses enumerateObjectsUsingBlock:^(id discuss, NSUInteger idx, BOOL * _Nonnull stop) {
+            if ([discuss isKindOfClass:objc_getClass("Discuss")]) {
+                [sessionList addObject:[weakSelf dictFromDiscussSearchResult:discuss searcherInter:inter]];
+            } else if([discuss isKindOfClass:objc_getClass("Group")]) {
+                [sessionList addObject:[weakSelf dictFromGroupSearchResult:discuss]];
+            }
+        }];
+        
+        [inter.searchedGroups enumerateObjectsUsingBlock:^(id group, NSUInteger idx, BOOL * _Nonnull stop) {
+            if ([group isKindOfClass:objc_getClass("Discuss")]) {
+                [sessionList addObject:[weakSelf dictFromDiscussSearchResult:group searcherInter:inter]];
+            } else if([group isKindOfClass:objc_getClass("Group")]) {
+                [sessionList addObject:[weakSelf dictFromGroupSearchResult:group]];
+            }
+        }];
+        
+        return [GCDWebServerDataResponse responseWithJSONObject:sessionList];
+    }];
+}
+
+- (NSMutableArray *)getRecentSessionList {
+    NSMutableArray *sessionList = [NSMutableArray array];
+
+    MQRecentSessionManager *manager = [objc_getClass("MQRecentSessionManager") sharedLogicEngine];
+    NSArray *recentList = [manager getSessionIDList];
+    GroupFolderManager *groupManager = [objc_getClass("GroupFolderManager") sharedFolderManager];
+    BHProfileManager *profileManager = [objc_getClass("BHProfileManager") sharedInstance];
+    BHDiscussGroupManager *discussgroupManager = [objc_getClass("BHDiscussGroupManager") sharedInstance];
+    UnreadMsgMgr *unreadMsgMgr = [objc_getClass("UnreadMsgMgr") sharedUnreadMsgMgr];
+    
+    [recentList enumerateObjectsUsingBlock:^(MQSessionID *obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        
+        BHMessageModel *msgModel = [unreadMsgMgr getRecentModelFromSessionID:obj];
+        NSString *subTitle = @"";
+        if ((msgModel != 0x0) && ([msgModel chatType] != 0x4000)) {
+            if ([msgModel msgType] != 0x4) {
+                if ([msgModel chatType] != 0x10000) {
+                    subTitle = [(NSMutableAttributedString *)[objc_getClass("MQRecentMsgTips") tipsOfContentMsg:msgModel sessionId:obj]  string];
+                }
+            }
+        }
+        
+        if(obj.chatType == 2) {
+            BHGroupModel *model = [groupManager BHGroupModelWithUin:obj.uinString];
+            if (model && [model isKindOfClass:objc_getClass("BHGroupModel")]) {
+                [sessionList addObject:[self dictFromBHGroupModel:model subTitle:subTitle]];
+            }
+        } else if (obj.chatType == 8) {
+            DiscussGroupInfo *discuss = [discussgroupManager getGroupInfo:obj.uin];
+            if (discuss && [discuss isKindOfClass:objc_getClass("DiscussGroupInfo")]) {
+                [sessionList addObject:[self dictFromDiscussGroupInfo:discuss subTitle:subTitle]];
+            }
+        } else {
+            BHProfileModel *profile = [profileManager getProfileWithUIN:obj.uinString];
+            if (profile && [profile isKindOfClass:objc_getClass("BHProfileModel")]) {
+                [sessionList addObject:[self dictFromBHProfileModel:profile subTitle:subTitle]];
+            }
+        }
+    }];
+    
+    return sessionList;
+}
+
+- (void)addHandleForSearchUserChatLog {
+    __weak typeof(self) weakSelf = self;
+    [self.webServer addHandlerForMethod:@"GET" path:@"/qq-plugin/chatlog" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
+        if (![weakSelf isLocalhost:request.headers[@"Host"]]) {
+            return [GCDWebServerResponse responseWithStatusCode:404];
+        }
+        NSString *userId = request.query ? request.query[@"userId"] ? request.query[@"userId"] : nil : nil;
+        int sessionType = request.query ? request.query[@"type"] ? [request.query[@"type"] intValue] : 0 : 0;
+        if (userId && sessionType != 0) {
+            int chatType = 0;
+            switch (sessionType) {
+                case 1:         //  好友
+                    chatType = 1;
+                    break;
+                case 101:       //  群
+                    chatType = 2;
+                    break;
+                case 201:      //  讨论组
+                    chatType = 8;
+                    break;
+            }
+            
+            __block BOOL hasResult = NO;
+            NSMutableArray *chatLogList = [NSMutableArray array];
+            TChatHistoryMsgManager *chatMgr = [objc_getClass("TChatHistoryMsgManager") sharedInstance];
+            TChatHistoryMsgModelWrapper * wrap = [chatMgr getHistoryMsgModel:[userId longLongValue] sessType:sessionType filter:0];
+            [wrap _loadMoreMessageUpForAllMsgType:^{
+                [[[wrap.msgArray reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(BHMessageModel *obj, NSUInteger idx, BOOL * _Nonnull stop) {
+                    [chatLogList addObject:[weakSelf dictFromMessageModel:obj]];
+                }];
+                hasResult = YES;
+            }];
+            
+            while (!hasResult) {}
+
+            GroupFolderManager *groupManager = [objc_getClass("GroupFolderManager") sharedFolderManager];
+            BHProfileManager *profileManager = [objc_getClass("BHProfileManager") sharedInstance];
+            BHDiscussGroupManager *discussgroupManager = [objc_getClass("BHDiscussGroupManager") sharedInstance];
+            NSString *title = @"";
+            if(chatType == 2) {
+                BHGroupModel *groupModel = [groupManager BHGroupModelWithUin:userId];
+                if (groupModel && [groupModel isKindOfClass:objc_getClass("BHGroupModel")]) {
+                    title = groupModel.groupName;
+                    if (IS_VALID_STRING(groupModel.groupRemark)) {
+                        title = [NSString stringWithFormat:@"%@ (%@)", groupModel.groupRemark, groupModel.groupName];
+                    }
+                }
+            } else if (chatType == 8) {
+                DiscussGroupInfo *discuss = [discussgroupManager getGroupInfo:[userId longLongValue]];
+                if (discuss && [discuss isKindOfClass:objc_getClass("DiscussGroupInfo")]) {
+                    title = discuss.name;
+                }
+            } else {
+                BHProfileModel *profile = [profileManager getProfileWithUIN:userId];
+                if (profile && [profile isKindOfClass:objc_getClass("BHProfileModel")]) {
+                    title = profile.displayName;
+                }
+            }
+            
+            NSDictionary *toUserContactDict = @{@"title": [NSString stringWithFormat:@"To: %@", title],
+                                                @"subTitle": chatLogList.count > 0 ? @"以下为聊天记录👇🏻" : @"",
+                                                @"icon": [weakSelf avatarPathWithUIN:userId isUser:sessionType == 1],
+                                                @"userId": userId,
+                                                @"qlurl": [weakSelf avatarPathWithUIN:userId isUser:sessionType == 1]
+                                                };
+            [chatLogList insertObject:toUserContactDict atIndex:0];
+            
+            return [GCDWebServerDataResponse responseWithJSONObject:chatLogList];
+        }
+        
+        return [GCDWebServerResponse responseWithStatusCode:404];
+    }];
+}
+
+- (void)addHandleForOpenSession {
+    __weak typeof(self) weakSelf = self;
+    [self.webServer addHandlerForMethod:@"POST" path:@"/QQ-plugin/open-session" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerURLEncodedFormRequest * _Nonnull request) {
+        if (![weakSelf isLocalhost:request.headers[@"Host"]]) {
+            return [GCDWebServerResponse responseWithStatusCode:404];
+        }
+        NSDictionary *requestBody = [request arguments];
+        
+        if (requestBody && requestBody[@"userId"]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                long long uin = [requestBody[@"userId"] longLongValue];
+                int sessionType = [requestBody[@"type"] intValue];
+                int chatType = 0;
+                switch (sessionType) {
+                    case 1:         //  好友
+                        chatType = 1;
+                        break;
+                    case 101:       //  群
+                        chatType = 2;
+                        break;
+                    case 201:      //  讨论组
+                        chatType = 8;
+                        break;
+                }
+                MQAIOManager *manager = [objc_getClass("MQAIOManager") sharedInstance];
+                [manager showAIOOfUin:uin chatType:chatType bringToTop:YES];
+                [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
+            });
+            return [GCDWebServerResponse responseWithStatusCode:200];
+        }
+        
+        return [GCDWebServerResponse responseWithStatusCode:404];
+    }];
+}
+
+- (void)addHandleForSendMsg {
+    __weak typeof(self) weakSelf = self;
+    [self.webServer addHandlerForMethod:@"POST" path:@"/QQ-plugin/send-message" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerURLEncodedFormRequest * _Nonnull request) {
+        if (![weakSelf isLocalhost:request.headers[@"Host"]]) {
+            return [GCDWebServerResponse responseWithStatusCode:404];
+        }
+        NSDictionary *requestBody = [request arguments];
+        if (requestBody && requestBody[@"userId"] && requestBody[@"content"]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                long long uin = [requestBody[@"userId"] longLongValue];
+                int sessionType = [requestBody[@"type"] intValue];
+                [TKMsgManager sendTextMessage:requestBody[@"content"] uin:uin sessionType:sessionType];
+                
+            });
+            return [GCDWebServerResponse responseWithStatusCode:200];
+        }
+        return [GCDWebServerResponse responseWithStatusCode:404];
+    }];
+}
+
+#pragma mark - 返回聊天记录的 dict
+- (NSDictionary *)dictFromMessageModel:(BHMessageModel *)msgModel {
+    NSString *title = @"[非文本信息]";
+    MQSessionID *sessionID = [objc_getClass("MQSessionID") sessionIdWithChatType:msgModel.chatType andUin:[msgModel.uin longLongValue]];
+    if ((msgModel != 0x0) && ([msgModel chatType] != 0x4000)) {
+        if ([msgModel msgType] != 0x4) {
+            if ([msgModel chatType] != 0x10000) {
+                title = [(NSMutableAttributedString *)[objc_getClass("MQRecentMsgTips") tipsOfContentMsg:msgModel sessionId:sessionID]  string];
+            }
+        }
+    }
+    NSString *subTitle = [self getDateStringWithTimeStr:msgModel.time];
+    NSArray *contentPartArray = [msgModel contentPartArray];
+    __block NSString *qlurl = @"";
+    BHMsgManager *msgMgr = [objc_getClass("BHMsgManager") sharedInstance];
+    NSFileManager *fileMgr = [NSFileManager defaultManager];
+    
+    [contentPartArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        if ([obj isKindOfClass:objc_getClass("QQImageMsgContentPart")]) {
+            qlurl = [msgMgr getImagePathByMsg:obj imageSize:0];
+            if (![fileMgr fileExistsAtPath:qlurl]) {
+                [msgMgr downloadImageByMsg:msgModel content:obj completion:nil ProgressBlock:nil];
+            }
+            *stop = YES;
+        }
+    }];
+    if (msgModel.msgType == 181) {
+         qlurl = [msgMgr getShortVideoPathByMsg:msgModel];
+        if (![fileMgr fileExistsAtPath:qlurl]) {
+            [objc_getClass("VideoMsgLoadManager") requsetVideoMsgVideo:msgModel completion:nil];
+        } 
+    }
+   
+    return @{@"title": title,
+             @"subTitle": subTitle,
+             @"icon": [self avatarPathWithUIN:msgModel.senderUin isUser:YES],
+             @"userId": msgModel.uin,
+             @"qlurl": qlurl
+             };
+}
+
+#pragma mark - 返回搜索用户的 dict
+- (NSDictionary *)dictFromBuddySearchResult:(Buddy *)buddy {
+    NSString *uin = [buddy valueForKey:@"_uinStr"];
+    BHFriendListManager *friendListManager = [buddy valueForKey:@"_friendListManager"];
+    
+    NSMutableDictionary *friendCache = [friendListManager valueForKey:@"_friendCache"];
+    NSMutableDictionary *groupCache = [friendListManager valueForKey:@"_groupCache"];
+    
+    BHFriendModel *friendModel = friendCache[uin];
+    NSString *title = friendModel.profileModel.nick;
+    if (IS_VALID_STRING(friendModel.remark)) {
+        title = [NSString stringWithFormat:@"%@(%@)",friendModel.remark, friendModel.profileModel.nick];
+    }
+    
+    NSString *subTitle = [NSString stringWithFormat:@"[%@]",friendModel.uin];
+    if (IS_VALID_STRING(friendModel.groupID) && groupCache[friendModel.groupID]) {
+        BHFriendGroupModel *groupModel = groupCache[friendModel.groupID];
+        subTitle = [NSString stringWithFormat:@"%@-[%@]",subTitle, groupModel.groupName];
+    }
+    
+    if (IS_VALID_STRING(friendModel.showName)) {
+        subTitle = [NSString stringWithFormat:@"%@-[%@]",subTitle, friendModel.showName];
+    }
+    
+    return @{@"title": title,
+             @"subTitle": subTitle,
+             @"icon": [self avatarPathWithUIN:friendModel.uin isUser:YES],
+             @"userId": friendModel.uin,
+             @"type": @"1",
+             };
+}
+
+- (NSDictionary *)dictFromDiscussSearchResult:(Discuss *)discuss searcherInter:(ContactSearcherInter *)inter {
+    if ([discuss.className isEqualToString:@"Group"]) {
+        return [self dictFromGroupSearchResult:(Group *)discuss];
+    }
+    
+    NSString *uin = [discuss valueForKey:@"_uinStr"];
+    DiscussGroupInfo *discussInfo = discuss.discussInfo;
+
+    NSString *title = discussInfo.flag == 1 ? discussInfo.name : [discuss combinDiscussName];
+    
+    __block NSString *subTitle = @"";
+    if (discuss.discussSearchType == 2) {
+        BHDiscussGroupManager *groupManager = [objc_getClass("BHDiscussGroupManager") sharedInstance];
+        
+        [inter.searchedBuddys enumerateObjectsUsingBlock:^(Buddy * _Nonnull buddy, NSUInteger idx, BOOL * _Nonnull stop) {
+            DiscussMemberInfo *member = [groupManager getDiscussMember:[discuss valueForKey:@"_uinStr"] memberUin:[[buddy valueForKey:@"_uinStr"] longLongValue]];
+            if (member) {
+                subTitle = [NSString stringWithFormat:@"包含:%@",member.remarkName];
+            }
+        }];
+    } else {
+        subTitle = [NSString stringWithFormat:@"共 %d 人",discussInfo.memberNum];
+    }
+    
+    return @{@"title": [NSString stringWithFormat:@"[讨论组]%@", title],
+             @"subTitle": subTitle,
+             @"icon": [self avatarPathWithUIN:uin isUser:NO],
+             @"userId": uin,
+             @"type": @"201",
+             };
+}
+
+- (NSDictionary *)dictFromGroupSearchResult:(Group *)group {
+    return [self dictFromBHGroupModel:group.troopModel subTitle:nil];
+}
+
+#pragma mark - 返回最近聊天列表的 dict
+
+/**
+ 返回好友的 dict
+
+ @param profileModel 当前好友的model
+ @param subTitle 该值为subTitle设置,空的话默认为QQ号,传值的话为一般为最新的聊天记录
+ */
+- (NSDictionary *)dictFromBHProfileModel:(BHProfileModel *)profileModel subTitle:(NSString *)subTitle {
+    NSString *title = profileModel.displayName;
+    return @{@"title": title,
+             @"subTitle": subTitle ?: profileModel.uin,
+             @"icon": [self avatarPathWithUIN:profileModel.uin isUser:YES],
+             @"userId": profileModel.uin,
+             @"type": @"1",
+             };
+}
+
+- (NSDictionary *)dictFromDiscussGroupInfo:(DiscussGroupInfo *)discussInfo subTitle:(NSString *)subTitle {
+    NSString *title = discussInfo.name;
+    NSString *subText = subTitle ?: [NSString stringWithFormat:@"共 %d 人",discussInfo.memberNum];
+    NSString *uin = [NSString stringWithFormat:@"%lld",discussInfo.groupUin];
+    NSString *iconPath = [self avatarPathWithUIN:uin isUser:NO];
+    NSFileManager *fileMgr = [NSFileManager defaultManager];
+    
+    if (![fileMgr fileExistsAtPath:iconPath]) {
+        //        一般讨论组的头像没有保存到本地,如果本地没有的话,就保存下
+        MQSessionID *sessionID = [objc_getClass("MQSessionID") sessionIdWithChatType:8 andUin:discussInfo.groupUin];
+        NSImage *image = [objc_getClass("TXImageUtils") imageOfSession:sessionID];
+        NSData *imageData = [image TIFFRepresentation];
+        [imageData writeToFile:iconPath atomically:YES];
+    }
+    
+    return @{@"title": [NSString stringWithFormat:@"[讨论组]%@", title],
+             @"subTitle": subText,
+             @"icon": iconPath,
+             @"userId": uin,
+             @"type": @"201",
+             };
+}
+
+- (NSDictionary *)dictFromBHGroupModel:(BHGroupModel *)groupModel subTitle:(NSString *)subTitle {
+    NSDictionary *dict = @{};
+    if (groupModel) {
+        NSString *title = groupModel.groupName;
+        if (IS_VALID_STRING(groupModel.groupRemark)) {
+            title = [NSString stringWithFormat:@"%@ (%@)", groupModel.groupRemark, groupModel.groupName];
+        }
+        
+        dict = @{@"title": [NSString stringWithFormat:@"[群聊]%@", title],
+                 @"subTitle": subTitle ?: [NSString stringWithFormat:@"共 %llu 人",groupModel.groupMemberCount],
+                 @"icon": [self avatarPathWithUIN:groupModel.groupCode isUser:NO],
+                 @"userId": groupModel.groupCode,
+                 @"type": @"101",
+                 };
+    }
+    return dict;
+}
+
+#pragma mark - Other
+- (NSString *)getDateStringWithTimeStr:(NSTimeInterval)time{
+    NSDate *date = [NSDate dateWithTimeIntervalSince1970:time];
+    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+    if ([date isToday]) {
+        formatter.dateFormat = @"HH:mm:ss";
+        return [formatter stringFromDate:date];
+    } else {
+        if ([date isYesterday]) {
+            formatter.dateFormat = @"昨天 HH:mm:ss";
+            return [formatter stringFromDate:date];
+        } else {
+            formatter.dateFormat = @"yy-MM-dd HH:mm:ss";
+            return [formatter stringFromDate:date];
+        }
+    }
+    return @"";
+}
+
+//  获取本地图片缓存路径
+- (NSString *)avatarPathWithUIN:(NSString *)uin isUser:(BOOL)isUser {
+    BHAvatarManager *manager = [objc_getClass("BHAvatarManager") sharedInstance];
+    NSString *imgPath;
+    if (isUser) {
+        imgPath = [manager userAvatarPathWithUIN:uin];
+    } else {
+        imgPath = [manager groupAvatarPathWithGroupCode:uin];
+    }
+    return imgPath ?: @"";
+}
+
+- (BOOL)isLocalhost:(NSString *)host {
+    NSArray *localhostUrls = @[[NSString stringWithFormat:@"127.0.0.1:%d", port],
+                               [NSString stringWithFormat:@"localhost:%d", port]
+                               ];
+    return [localhostUrls containsObject:host];
+}
+
+@end

+ 210 - 0
QQPlugin/Vendor/fishhook.c

@@ -0,0 +1,210 @@
+// Copyright (c) 2013, Facebook, Inc.
+// All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name Facebook nor the names of its contributors may be used to
+//     endorse or promote products derived from this software without specific
+//     prior written permission.
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#import "fishhook.h"
+
+#import <dlfcn.h>
+#import <stdlib.h>
+#import <string.h>
+#import <sys/types.h>
+#import <mach-o/dyld.h>
+#import <mach-o/loader.h>
+#import <mach-o/nlist.h>
+
+#ifdef __LP64__
+typedef struct mach_header_64 mach_header_t;
+typedef struct segment_command_64 segment_command_t;
+typedef struct section_64 section_t;
+typedef struct nlist_64 nlist_t;
+#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
+#else
+typedef struct mach_header mach_header_t;
+typedef struct segment_command segment_command_t;
+typedef struct section section_t;
+typedef struct nlist nlist_t;
+#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
+#endif
+
+#ifndef SEG_DATA_CONST
+#define SEG_DATA_CONST  "__DATA_CONST"
+#endif
+
+struct rebindings_entry {
+  struct rebinding *rebindings;
+  size_t rebindings_nel;
+  struct rebindings_entry *next;
+};
+
+static struct rebindings_entry *_rebindings_head;
+
+static int prepend_rebindings(struct rebindings_entry **rebindings_head,
+                              struct rebinding rebindings[],
+                              size_t nel) {
+  struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
+  if (!new_entry) {
+    return -1;
+  }
+  new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
+  if (!new_entry->rebindings) {
+    free(new_entry);
+    return -1;
+  }
+  memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
+  new_entry->rebindings_nel = nel;
+  new_entry->next = *rebindings_head;
+  *rebindings_head = new_entry;
+  return 0;
+}
+
+static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
+                                           section_t *section,
+                                           intptr_t slide,
+                                           nlist_t *symtab,
+                                           char *strtab,
+                                           uint32_t *indirect_symtab) {
+  uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
+  void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
+  for (uint i = 0; i < section->size / sizeof(void *); i++) {
+    uint32_t symtab_index = indirect_symbol_indices[i];
+    if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
+        symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {
+      continue;
+    }
+    uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
+    char *symbol_name = strtab + strtab_offset;
+    if (strnlen(symbol_name, 2) < 2) {
+      continue;
+    }
+    struct rebindings_entry *cur = rebindings;
+    while (cur) {
+      for (uint j = 0; j < cur->rebindings_nel; j++) {
+        if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
+          if (cur->rebindings[j].replaced != NULL &&
+              indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
+            *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
+          }
+          indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
+          goto symbol_loop;
+        }
+      }
+      cur = cur->next;
+    }
+  symbol_loop:;
+  }
+}
+
+static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
+                                     const struct mach_header *header,
+                                     intptr_t slide) {
+  Dl_info info;
+  if (dladdr(header, &info) == 0) {
+    return;
+  }
+
+  segment_command_t *cur_seg_cmd;
+  segment_command_t *linkedit_segment = NULL;
+  struct symtab_command* symtab_cmd = NULL;
+  struct dysymtab_command* dysymtab_cmd = NULL;
+
+  uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
+  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
+    cur_seg_cmd = (segment_command_t *)cur;
+    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
+      if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
+        linkedit_segment = cur_seg_cmd;
+      }
+    } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
+      symtab_cmd = (struct symtab_command*)cur_seg_cmd;
+    } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
+      dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
+    }
+  }
+
+  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
+      !dysymtab_cmd->nindirectsyms) {
+    return;
+  }
+
+  // Find base symbol/string table addresses
+  uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
+  nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
+  char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
+
+  // Get indirect symbol table (array of uint32_t indices into symbol table)
+  uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
+
+  cur = (uintptr_t)header + sizeof(mach_header_t);
+  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
+    cur_seg_cmd = (segment_command_t *)cur;
+    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
+      if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
+          strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
+        continue;
+      }
+      for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
+        section_t *sect =
+          (section_t *)(cur + sizeof(segment_command_t)) + j;
+        if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
+          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
+        }
+        if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
+          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
+        }
+      }
+    }
+  }
+}
+
+static void _rebind_symbols_for_image(const struct mach_header *header,
+                                      intptr_t slide) {
+    rebind_symbols_for_image(_rebindings_head, header, slide);
+}
+
+int rebind_symbols_image(void *header,
+                         intptr_t slide,
+                         struct rebinding rebindings[],
+                         size_t rebindings_nel) {
+    struct rebindings_entry *rebindings_head = NULL;
+    int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
+    rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
+    free(rebindings_head);
+    return retval;
+}
+
+int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
+  int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
+  if (retval < 0) {
+    return retval;
+  }
+  // If this was the first call, register callback for image additions (which is also invoked for
+  // existing images, otherwise, just run on existing images
+  if (!_rebindings_head->next) {
+    _dyld_register_func_for_add_image(_rebind_symbols_for_image);
+  } else {
+    uint32_t c = _dyld_image_count();
+    for (uint32_t i = 0; i < c; i++) {
+      _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
+    }
+  }
+  return retval;
+}

+ 76 - 0
QQPlugin/Vendor/fishhook.h

@@ -0,0 +1,76 @@
+// Copyright (c) 2013, Facebook, Inc.
+// All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name Facebook nor the names of its contributors may be used to
+//     endorse or promote products derived from this software without specific
+//     prior written permission.
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef fishhook_h
+#define fishhook_h
+
+#include <stddef.h>
+#include <stdint.h>
+
+#if !defined(FISHHOOK_EXPORT)
+#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
+#else
+#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif //__cplusplus
+
+/*
+ * A structure representing a particular intended rebinding from a symbol
+ * name to its replacement
+ */
+struct rebinding {
+  const char *name;
+  void *replacement;
+  void **replaced;
+};
+
+/*
+ * For each rebinding in rebindings, rebinds references to external, indirect
+ * symbols with the specified name to instead point at replacement for each
+ * image in the calling process as well as for all future images that are loaded
+ * by the process. If rebind_functions is called more than once, the symbols to
+ * rebind are added to the existing list of rebindings, and if a given symbol
+ * is rebound more than once, the later rebinding will take precedence.
+ */
+FISHHOOK_VISIBILITY
+int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
+
+/*
+ * Rebinds as above, but only in the specified image. The header should point
+ * to the mach-o header, the slide should be the slide offset. Others as above.
+ */
+FISHHOOK_VISIBILITY
+int rebind_symbols_image(void *header,
+                         intptr_t slide,
+                         struct rebinding rebindings[],
+                         size_t rebindings_nel);
+
+#ifdef __cplusplus
+}
+#endif //__cplusplus
+
+#endif //fishhook_h
+

+ 17 - 0
QQPlugin/Views/AutoReply/TKAutoReplyCell.h

@@ -0,0 +1,17 @@
+//
+//  TKAutoReplyCell.h
+//  QQPlugin
+//
+//  Created by TK on 2017/8/21.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "TKAutoReplyModel.h"
+
+@interface TKAutoReplyCell : NSControl
+
+@property (nonatomic, strong) TKAutoReplyModel *model;
+@property (nonatomic, copy) void (^updateModel)(void);
+
+@end

+ 91 - 0
QQPlugin/Views/AutoReply/TKAutoReplyCell.m

@@ -0,0 +1,91 @@
+//
+//  TKAutoReplyCell.m
+//  QQPlugin
+//
+//  Created by TK on 2017/8/21.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKAutoReplyCell.h"
+#import "QQPlugin.h"
+
+@interface TKAutoReplyCell ()
+
+@property (nonatomic, strong) NSButton *selectBtn;
+@property (nonatomic, strong) NSTextField *keywordLabel;
+@property (nonatomic, strong) NSTextField *replyContentLabel;
+@property (nonatomic, strong) NSBox *bottomLine;
+
+@end
+
+@implementation TKAutoReplyCell
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self initSubviews];
+    }
+    return self;
+}
+
+- (void)initSubviews {
+    self.selectBtn = ({
+        NSButton *btn = [NSButton tk_checkboxWithTitle:@"" target:self action:@selector(clickSelectBtn:)];
+        btn.frame = NSMakeRect(5, 15, 20, 20);
+        
+        btn;
+    });
+    
+    self.keywordLabel = ({
+        NSTextField *label = [NSTextField tk_labelWithString:@""];
+        label.placeholderString = @"关键字";
+        [[label cell] setLineBreakMode:NSLineBreakByCharWrapping];
+        [[label cell] setTruncatesLastVisibleLine:YES];
+        label.font = [NSFont systemFontOfSize:10];
+        label.frame = NSMakeRect(30, 30, 160, 15);
+        
+        label;
+    });
+    
+    self.replyContentLabel = ({
+        NSTextField *label = [NSTextField tk_labelWithString:@""];
+        label.placeholderString = @"回复内容";
+        [[label cell] setLineBreakMode:NSLineBreakByCharWrapping];
+        [[label cell] setTruncatesLastVisibleLine:YES];
+        label.frame = NSMakeRect(30, 10, 160, 15);
+        
+        label;
+    });
+    
+    self.bottomLine = ({
+        NSBox *v = [[NSBox alloc] init];
+        v.boxType = NSBoxSeparator;
+        v.frame = NSMakeRect(0, 0, 200, 1);
+        
+        v;
+    });
+    
+    [self addSubviews:@[self.selectBtn,
+                        self.keywordLabel,
+                        self.replyContentLabel,
+                        self.bottomLine]];
+}
+
+- (void)clickSelectBtn:(NSButton *)btn {
+    self.model.enable = btn.state;
+    if (!self.model.enableSingleReply && !self.model.enableGroupReply && btn.state == YES) {
+        self.model.enableSingleReply = YES;
+        if (self.updateModel) self.updateModel();
+    }
+}
+
+- (void)setModel:(TKAutoReplyModel *)model {
+    _model = model;
+    if (model.keyword == nil && model.replyContent == nil) return;
+    
+    self.selectBtn.state = model.enable;
+    self.keywordLabel.stringValue = model.keyword != nil ? model.keyword : @"";
+    self.replyContentLabel.stringValue = model.replyContent != nil ? model.replyContent : @"";
+}
+
+@end

+ 17 - 0
QQPlugin/Views/AutoReply/TKAutoReplyContentView.h

@@ -0,0 +1,17 @@
+//
+//  TKAutoReplyContentView.h
+//  QQPlugin
+//
+//  Created by TK on 2017/8/20.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "TKAutoReplyModel.h"
+
+@interface TKAutoReplyContentView : NSView
+
+@property (nonatomic, strong) TKAutoReplyModel *model;
+@property (nonatomic, copy) void (^endEdit)(void);
+
+@end

+ 205 - 0
QQPlugin/Views/AutoReply/TKAutoReplyContentView.m

@@ -0,0 +1,205 @@
+//
+//  TKAutoReplyContentView.m
+//  QQPlugin
+//
+//  Created by TK on 2017/8/20.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKAutoReplyContentView.h"
+#import "QQPlugin.h"
+
+@interface TKAutoReplyContentView () <NSTextFieldDelegate>
+
+@property (nonatomic, strong) NSTextField *keywordLabel;
+@property (nonatomic, strong) NSTextField *keywordTextField;
+@property (nonatomic, strong) NSTextField *autoReplyLabel;
+@property (nonatomic, strong) NSTextField *autoReplyContentField;
+@property (nonatomic, strong) NSButton *enableGroupReplyBtn;
+@property (nonatomic, strong) NSButton *enableSingleReplyBtn;
+@property (nonatomic, strong) NSButton *enableRegexBtn;
+@property (nonatomic, strong) NSTextField *delayField;
+@property (nonatomic, strong) NSButton *enableDelayBtn;
+
+@end
+
+@implementation TKAutoReplyContentView
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self initSubviews];
+    }
+    return self;
+}
+
+- (void)initSubviews {
+    self.enableRegexBtn = ({
+        NSButton *btn = [NSButton tk_checkboxWithTitle:@"开启正则匹配" target:self action:@selector(clickEnableRegexBtn:)];
+        btn.frame = NSMakeRect(20, 15, 400, 20);
+        
+        btn;
+    });
+    
+    self.enableGroupReplyBtn = ({
+        NSButton *btn = [NSButton tk_checkboxWithTitle:@"开启群聊自动回复" target:self action:@selector(clickEnableGroupBtn:)];
+        btn.frame = NSMakeRect(20, 40, 400, 20);
+        
+        btn;
+    });
+    
+    self.enableSingleReplyBtn = ({
+        NSButton *btn = [NSButton tk_checkboxWithTitle:@"开启私聊自动回复" target:self action:@selector(clickEnableSingleBtn:)];
+        btn.frame = NSMakeRect(200, 40, 400, 20);
+        
+        btn;
+    });
+    
+    self.enableDelayBtn = ({
+        NSButton *btn = [NSButton tk_checkboxWithTitle:@"延迟发送" target:self action:@selector(clickEnableDelayBtn:)];
+        btn.frame = NSMakeRect(200, 15, 72, 20);
+        
+        btn;
+    });
+
+    self.delayField = ({
+        NSTextField *textField = [[NSTextField alloc] init];
+        textField.frame = NSMakeRect(CGRectGetMaxX(self.enableDelayBtn.frame), 15, 60, 20);
+        textField.placeholderString = @"秒";
+        textField.delegate = self;
+        textField.alignment = NSTextAlignmentRight;
+        NSNumberFormatter * formater = [[NSNumberFormatter alloc] init];
+        formater.numberStyle = NSNumberFormatterDecimalStyle;
+        formater.minimum = @(0);
+        formater.maximum = @(999);
+        textField.cell.formatter = formater;
+        
+        textField;
+    });
+    
+    self.autoReplyContentField = ({
+        NSTextField *textField = [[NSTextField alloc] init];
+        textField.frame = NSMakeRect(20, 70, 350, 175);
+        textField.placeholderString = @"请输入自动回复的内容(‘|’ 为随机回复其中任一内容)";
+        textField.delegate = self;
+        
+        textField;
+    });
+    
+    self.autoReplyLabel = ({
+        NSTextField *label = [NSTextField tk_labelWithString:@"自动回复:"];
+        label.frame = NSMakeRect(20, 250, 350, 20);
+        
+        label;
+    });
+    
+    self.keywordTextField = ({
+        NSTextField *textField = [[NSTextField alloc] init];
+        textField.frame = NSMakeRect(20, 290, 350, 50);
+        textField.placeholderString = @"请输入关键字( ‘*’ 为任何消息都回复,‘|’ 为匹配多个关键字)";
+        textField.delegate = self;
+        
+        textField;
+    });
+    
+    self.keywordLabel = ({
+        NSTextField *label = [NSTextField tk_labelWithString:@"关键字:"];
+        label.frame = NSMakeRect(20, 345, 350, 20);
+        
+        label;
+    });
+    
+    [self addSubviews:@[self.enableRegexBtn,
+                        self.enableGroupReplyBtn,
+                        self.enableSingleReplyBtn,
+                        self.autoReplyContentField,
+                        self.autoReplyLabel,
+                        self.keywordTextField,
+                        self.keywordLabel,
+                        self.delayField,
+                        self.enableDelayBtn]];
+}
+
+- (void)clickEnableRegexBtn:(NSButton *)btn {
+    self.model.enableRegex = btn.state;
+}
+
+- (void)clickEnableGroupBtn:(NSButton *)btn {
+    self.model.enableGroupReply = btn.state;
+    if (btn.state) {
+        self.model.enable = YES;
+    } else if(!self.model.enableSingleReply) {
+        self.model.enable = NO;
+    }
+    
+    if (self.endEdit) self.endEdit();
+}
+
+- (void)clickEnableSingleBtn:(NSButton *)btn {
+    self.model.enableSingleReply = btn.state;
+    if (btn.state) {
+        self.model.enable = YES;
+    } else if(!self.model.enableGroupReply) {
+        self.model.enable = NO;
+    }
+    if (self.endEdit) self.endEdit();
+}
+
+- (void)clickEnableDelayBtn:(NSButton *)btn {
+    self.model.enableDelay = btn.state;
+}
+
+- (void)viewDidMoveToSuperview {
+    [super viewDidMoveToSuperview];
+    self.layer.backgroundColor = [kBG2 CGColor];
+    self.layer.borderWidth = 1;
+    self.layer.borderColor = [TK_RGBA(0, 0, 0, 0.1) CGColor];
+    self.layer.cornerRadius = 3;
+    self.layer.masksToBounds = YES;
+    [self.layer setNeedsDisplay];
+}
+
+- (void)setModel:(TKAutoReplyModel *)model {
+    _model = model;
+    self.keywordTextField.stringValue = model.keyword != nil ? model.keyword : @"";
+    self.autoReplyContentField.stringValue = model.replyContent != nil ? model.replyContent : @"";
+    self.enableGroupReplyBtn.state = model.enableGroupReply;
+    self.enableSingleReplyBtn.state = model.enableSingleReply;
+    self.enableRegexBtn.state = model.enableRegex;
+    self.enableDelayBtn.state = model.enableDelay;
+    self.delayField.stringValue = [NSString stringWithFormat:@"%ld",model.delayTime];
+}
+
+- (void)controlTextDidEndEditing:(NSNotification *)notification {
+    if (self.endEdit) self.endEdit();
+}
+
+- (void)controlTextDidChange:(NSNotification *)notification {
+    NSControl *control = notification.object;
+    if (control == self.keywordTextField) {
+        self.model.keyword = self.keywordTextField.stringValue;
+    } else if (control == self.autoReplyContentField) {
+        self.model.replyContent = self.autoReplyContentField.stringValue;
+    } else if (control == self.delayField) {
+        self.model.delayTime = [self.delayField.stringValue integerValue];
+    }
+}
+
+- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
+    BOOL result = NO;
+    
+    if (commandSelector == @selector(insertNewline:)) {
+        [textView insertNewlineIgnoringFieldEditor:self];
+        result = YES;
+    } else if (commandSelector == @selector(insertTab:)) {
+        if (control == self.keywordTextField) {
+            [self.autoReplyContentField becomeFirstResponder];
+        } else if (control == self.autoReplyContentField) {
+            [self.keywordTextField becomeFirstResponder];
+        }
+    }
+    
+    return result;
+}
+
+@end

+ 16 - 0
QQPlugin/WindowControllers/AutoReply/TKAutoReplyWindowController.h

@@ -0,0 +1,16 @@
+//
+//  TKAutoReplyWindowController.h
+//  QQPlugin
+//
+//  Created by TK on 2017/4/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "TKAutoReplyModel.h"
+
+@interface TKAutoReplyWindowController : NSWindowController
+
+@property (nonatomic, copy) TKAutoReplyModel *model;
+
+@end

+ 239 - 0
QQPlugin/WindowControllers/AutoReply/TKAutoReplyWindowController.m

@@ -0,0 +1,239 @@
+//
+//  TKAutoReplyWindowController.m
+//  QQPlugin
+//
+//  Created by TK on 2017/4/19.
+//  Copyright © 2017年 tk. All rights reserved.
+//
+
+#import "TKAutoReplyWindowController.h"
+#import "TKAutoReplyContentView.h"
+#import "QQPlugin.h"
+#import "TKAutoReplyCell.h"
+#import "TKQQPluginConfig.h"
+
+@interface TKAutoReplyWindowController () <NSWindowDelegate, NSTableViewDelegate, NSTableViewDataSource>
+
+@property (nonatomic, strong) NSTableView *tableView;
+@property (nonatomic, strong) TKAutoReplyContentView *contentView;
+@property (nonatomic, strong) NSButton *addButton;
+@property (nonatomic, strong) NSButton *reduceButton;
+@property (nonatomic, strong) NSAlert *alert;
+
+@property (nonatomic, strong) NSMutableArray *autoReplyModels;
+@property (nonatomic, assign) NSInteger lastSelectIndex;
+
+@end
+
+@implementation TKAutoReplyWindowController
+
+- (void)windowDidLoad {
+    [super windowDidLoad];
+    
+    [self initSubviews];
+    [self setup];
+}
+
+- (void)showWindow:(id)sender {
+    [super showWindow:sender];
+    [self.tableView reloadData];
+    [self.contentView setHidden:YES];
+    if (self.autoReplyModels && self.autoReplyModels.count == 0) {
+        [self addModel];
+    }
+    if (self.autoReplyModels.count > 0 && self.tableView) {
+         [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:self.autoReplyModels.count - 1] byExtendingSelection:YES];
+    }
+}
+
+- (void)initSubviews {
+    NSScrollView *scrollView = ({
+        NSScrollView *scrollView = [[NSScrollView alloc] init];
+        scrollView.hasVerticalScroller = YES;
+        scrollView.frame = NSMakeRect(30, 50, 200, 375);
+        scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
+        
+        scrollView;
+    });
+    
+    self.tableView = ({
+        NSTableView *tableView = [[NSTableView alloc] init];
+        tableView.frame = scrollView.bounds;
+        tableView.allowsTypeSelect = YES;
+        tableView.delegate = self;
+        tableView.dataSource = self;
+        NSTableColumn *column = [[NSTableColumn alloc] init];
+        column.title = @"自动回复列表";
+        column.width = 200;
+        [tableView addTableColumn:column];
+        
+        tableView;
+    });
+    
+    self.contentView = ({
+        TKAutoReplyContentView *contentView = [[TKAutoReplyContentView alloc] init];
+        contentView.frame = NSMakeRect(250, 50, 400, 375);
+        contentView.hidden = YES;
+        
+        contentView;
+    });
+    
+    self.addButton = ({
+        NSButton *btn = [NSButton tk_buttonWithTitle:@"+" target:self action:@selector(addModel)];
+        btn.frame = NSMakeRect(30, 10, 40, 40);
+        btn.bezelStyle = NSBezelStyleTexturedRounded;
+        
+        btn;
+    });
+    
+    self.reduceButton = ({
+        NSButton *btn = [NSButton tk_buttonWithTitle:@"-" target:self action:@selector(reduceModel)];
+        btn.frame = NSMakeRect(80, 10, 40, 40);
+        btn.bezelStyle = NSBezelStyleTexturedRounded;
+        btn.enabled = NO;
+        
+        btn;
+    });
+    
+    self.alert = ({
+        NSAlert *alert = [[NSAlert alloc] init];
+        [alert addButtonWithTitle:@"确定"];
+        [alert setMessageText:@"您还有一条自动回复设置未完成"];
+        [alert setInformativeText:@"请完善未完成的自动回复设置"];
+        
+        alert;
+    });
+    
+    scrollView.contentView.documentView = self.tableView;
+    
+    [self.window.contentView addSubviews:@[scrollView,
+                                           self.contentView,
+                                           self.addButton,
+                                           self.reduceButton]];
+}
+
+- (void)setup {
+    self.window.title = @"自动回复设置";
+    self.window.contentView.layer.backgroundColor = [kBG1 CGColor];
+    [self.window.contentView.layer setNeedsDisplay];
+    
+    self.lastSelectIndex = -1;
+    self.autoReplyModels = [[TKQQPluginConfig sharedConfig] autoReplyModels];
+    [self.tableView reloadData];
+    
+    __weak typeof(self) weakSelf = self;
+    self.contentView.endEdit = ^(void) {
+        [weakSelf.tableView reloadData];
+        if (weakSelf.lastSelectIndex != -1) {
+            [weakSelf.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:weakSelf.lastSelectIndex] byExtendingSelection:YES];
+        }
+    };
+}
+
+/**
+ 关闭窗口事件
+ 
+ */
+- (BOOL)windowShouldClose:(id)sender {
+    [[TKQQPluginConfig sharedConfig] saveAutoReplyModels];
+    return YES;
+}
+
+#pragma mark - addButton & reduceButton ClickAction
+- (void)addModel {
+    if (self.contentView.hidden) {
+        self.contentView.hidden = NO;
+    }
+    __block NSInteger emptyModelIndex = -1;
+    [self.autoReplyModels enumerateObjectsUsingBlock:^(TKAutoReplyModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
+        if (model.hasEmptyKeywordOrReplyContent) {
+            emptyModelIndex = idx;
+            *stop = YES;
+        }
+    }];
+    
+    if (self.autoReplyModels.count > 0 && emptyModelIndex != -1) {
+        [self.alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
+            if(returnCode == NSAlertFirstButtonReturn){
+                if (self.tableView.selectedRow != -1) {
+                    [self.tableView deselectRow:self.tableView.selectedRow];
+                }
+                [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:emptyModelIndex] byExtendingSelection:YES];
+            }
+        }];
+        return;
+    };
+    
+    TKAutoReplyModel *model = [[TKAutoReplyModel alloc] init];
+    [self.autoReplyModels addObject:model];
+    [self.tableView reloadData];
+    self.contentView.model = model;
+    
+    [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:self.autoReplyModels.count - 1] byExtendingSelection:YES];
+}
+
+- (void)reduceModel {
+    NSInteger index = self.tableView.selectedRow;
+    if (index > -1) {
+        [self.autoReplyModels removeObjectAtIndex:index];
+        [self.tableView reloadData];
+        if (self.autoReplyModels.count == 0) {
+            self.contentView.hidden = YES;
+            self.reduceButton.enabled = NO;
+        } else {
+            [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:self.autoReplyModels.count - 1] byExtendingSelection:YES];
+        }
+    }
+}
+
+#pragma mark - NSTableViewDataSource && NSTableViewDelegate
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
+    return self.autoReplyModels.count;
+}
+
+- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
+    TKAutoReplyCell *cell = [[TKAutoReplyCell alloc] init];
+    cell.frame = NSMakeRect(0, 0, self.tableView.frame.size.width, 40);
+    cell.model = self.autoReplyModels[row];
+     __weak typeof(self) weakSelf = self;
+    cell.updateModel = ^{
+        weakSelf.contentView.model = weakSelf.autoReplyModels[row];
+    };
+    return cell;
+}
+
+- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row {
+    return 50;
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)notification {
+    NSTableView *tableView = notification.object;
+    self.contentView.hidden = tableView.selectedRow == -1;
+    self.reduceButton.enabled = tableView.selectedRow != -1;
+    
+    if (tableView.selectedRow != -1) {
+        TKAutoReplyModel *model = self.autoReplyModels[tableView.selectedRow];
+        self.contentView.model = model;
+        self.lastSelectIndex = tableView.selectedRow;
+        __block NSInteger emptyModelIndex = -1;
+        [self.autoReplyModels enumerateObjectsUsingBlock:^(TKAutoReplyModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
+            if (model.hasEmptyKeywordOrReplyContent) {
+                emptyModelIndex = idx;
+                *stop = YES;
+            }
+        }];
+        
+        if (emptyModelIndex != -1 && tableView.selectedRow != emptyModelIndex) {
+            [self.alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
+                if(returnCode == NSAlertFirstButtonReturn){
+                    if (self.tableView.selectedRow != -1) {
+                        [self.tableView deselectRow:self.tableView.selectedRow];
+                    }
+                    [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:emptyModelIndex] byExtendingSelection:YES];
+                }
+            }];
+        }
+    }
+}
+
+@end

+ 31 - 0
QQPlugin/WindowControllers/AutoReply/TKAutoReplyWindowController.xib

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="TKAutoReplyWindowController">
+            <connections>
+                <outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" animationBehavior="default" id="F0z-JX-Cv5">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
+            <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+            <rect key="contentRect" x="196" y="240" width="668" height="452"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1280" height="778"/>
+            <view key="contentView" wantsLayer="YES" id="se5-gp-TjO">
+                <rect key="frame" x="0.0" y="0.0" width="668" height="452"/>
+                <autoresizingMask key="autoresizingMask"/>
+            </view>
+            <connections>
+                <outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
+            </connections>
+            <point key="canvasLocation" x="-52.5" y="145.5"/>
+        </window>
+    </objects>
+</document>

+ 15 - 0
QQPlugin/main.mm

@@ -0,0 +1,15 @@
+//
+//  main.m
+//  QQPlugin
+//
+//  Created by TK on 2018/3/18.
+//  Copyright © 2018年 TK. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "QQ+hook.h"
+
+static void __attribute__((constructor)) initialize(void) {
+    NSLog(@"++++++++ QQ loaded ++++++++");
+    [NSObject hookQQ];
+}

+ 95 - 0
README.md

@@ -0,0 +1,95 @@
+
+## QQPlugin-MacOS
+
+![platform](https://img.shields.io/badge/platform-macos-lightgrey.svg)    [![GitHub license](https://img.shields.io/github/license/TKkk-iOSer/QQPlugin-MacOS.svg)](https://github.com/TKkk-iOSer/QQPlugin-MacOS/blob/master/LICENSE)
+
+QQ 小助手 
+
+---
+
+### 功能
+
+* 消息自动回复
+* 消息防撤回
+* alfred 快捷发送消息 & 打开窗口 & 查看聊天记录
+
+---
+
+### 更新日志
+* 新增 alfred 开关(2018-10-26)   
+* 修复安全漏洞(2018-10-23)
+
+---
+### 安装
+
+~~第一次安装需要输入密码,仅是为了获取写入微信文件夹的权限~~
+
+**0. 懒癌版安装(适合非程序猿 && 有安装 git)**
+
+打开`应用程序-实用工具-Terminal(终端)`,执行以下命令并根据提示输入密码即可。**(需要git支持)**
+
+`cd ~/Downloads && rm -rf QQPlugin-MacOS && git clone https://github.com/TKkk-iOSer/QQPlugin-MacOS.git --depth=1 && ./QQPlugin-MacOS/Other/Install.sh`
+
+**1. 普通安装**
+
+* 点击`clone or download`按钮下载 QQPlugin 并解压,打开Terminal(终端),拖动解压后`Install.sh` 文件(在 Other 文件夹中)到 Terminal 回车即可。
+
+---
+
+### Demo 演示
+
+* 自动回复&防撤回    
+![自动回复&防撤回](./Other/ScreenShots/demo_reply_and_revoke.gif)
+
+* Alfred扩展    
+![快捷回复](./Other/ScreenShots/demo_alfred.gif)
+
+---
+
+### 使用
+
+* 消息防撤回:点击`开启消息防撤回`或者快捷键`command + shift + t`,即可开启、关闭。
+* 自动回复:点击`开启自动回复`或者快捷键`conmand + shift + k`,将弹出自动回复设置的窗口,点击红色箭头的按钮设置开关。    
+
+>若关键字为 `*`,则任何信息都回复;
+>若关键字为`x|y`,则 x 和 y 都回复;
+>若关键字**或者**自动回复为空,则不开启该条自动回复;
+>若开启正则,请确认正则表达式书写正确,[在线正则表达式测试](http://tool.oschina.net/regex/)
+> 可设置延迟发送回复,单位:秒.
+
+![自动回复设置.png](./Other/ScreenShots/auto_reply.png)
+
+* Alfred 使用:跟 微信`Alfred`类似,关键字为`q`. 
+
+---
+
+### 卸载
+
+将项目中的`./Other/Uninstall.sh`拖到`Terminal`(终端)运行即可.
+
+---
+
+### 依赖
+
+* [XMLReader](https://github.com/amarcadet/XMLReader)
+* [insert_dylib](https://github.com/Tyilo/insert_dylib)
+* [fishhook](https://github.com/facebook/fishhook)
+* [GCDWebServer](https://github.com/swisspol/GCDWebServer)   
+* [Alfred-Workflow](http://www.deanishe.net/alfred-workflow/index.html)
+
+---
+
+### Other
+
+~~若有其他好的想法欢迎 Issue me~~
+
+更新是不可能更新的,这辈子都不可能更新,只能逛逛github才能维持生活这样子![](https://wx3.sinaimg.cn/mw690/b13f6d6cgy1fc3a1kimfxj201v01xjr6.jpg)
+
+---
+
+### 免责声明
+* 使用插件有风险,使用需谨慎。
+* 本项目不可用于商业和个人其他意图。若使用不当,请使用者自行承担。
+* 如有侵权,请联系本人。tkk.ioser@gmail.com
+
+