├── .gitignore ├── Makefile ├── README.md ├── Screenshots └── ScreenShot1.PNG ├── XhsPlus.plist ├── XhsPlus.x ├── XhsSettingsViewController.h ├── XhsSettingsViewController.m └── control /.gitignore: -------------------------------------------------------------------------------- 1 | .theos/ 2 | packages/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := iphone:clang:latest:7.0 2 | INSTALL_TARGET_PROCESSES = discover 3 | 4 | ROOTLESS = 1 5 | ifeq ($(ROOTLESS),1) 6 | THEOS_PACKAGE_SCHEME=rootless 7 | endif 8 | ifeq ($(THEOS_PACKAGE_SCHEME), rootless) 9 | TARGET = iphone:clang:latest:15.0 10 | else 11 | TARGET = iphone:clang:latest:12.0 12 | endif 13 | 14 | include $(THEOS)/makefiles/common.mk 15 | 16 | TWEAK_NAME = XhsPlus 17 | 18 | XhsPlus_FILES = XhsPlus.x XhsSettingsViewController.m 19 | XhsPlus_CFLAGS = -fobjc-arc 20 | 21 | include $(THEOS_MAKE_PATH)/tweak.mk 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 使用须知 2 | 本项目为开源非营利性项目,仅用于iOS逆向学习交流,不得作为非法用途。 3 | 4 | 目前仅提供源码,所有deb以及注入ipa与作者无关,并且**强烈建议您自行下载源码编译而不是安装来路不明的安装包**。 5 | 6 | **双击关于界面的版本号打开插件设置界面。** 7 | 8 | 这是本人第一次接触iOS开发,最初目的是替代图层自用,感谢苏苏等大佬基于这个插件进行开发,我自己只有Linux环境和MAC OS on VMWare,所以很难继续开发,关于实况水印和评论区水印可以借鉴Quantumult X等插件思路,个人能力有限,因此不再继续开发。 9 | 10 | # TO DO 11 | 12 | 1.去除实况照片(Live Photo)水印 13 | -------------------------------------------------------------------------------- /Screenshots/ScreenShot1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wtrwx/XhsPlus/93b5cdc326fa4ce28872216178e76b458a0257e8/Screenshots/ScreenShot1.PNG -------------------------------------------------------------------------------- /XhsPlus.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.xingin.discover" ); }; } 2 | -------------------------------------------------------------------------------- /XhsPlus.x: -------------------------------------------------------------------------------- 1 | #import 2 | #import "XhsSettingsViewController.h" 3 | 4 | @interface TTTAttributedLabel : UILabel 5 | @property (nonatomic, assign) NSTimeInterval lastClickTime; 6 | @end 7 | 8 | static NSTimeInterval lastClickTime = 0; 9 | 10 | %hook TTTAttributedLabel 11 | 12 | - (void)touchesBegan:(id)arg1 withEvent:(id)arg2 { 13 | if (lastClickTime == 0) { 14 | NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; 15 | lastClickTime = currentTime; 16 | NSLog(@"[XhsPlus] 初始化 lastClickTime: %f", lastClickTime); 17 | %orig; 18 | return; 19 | } 20 | 21 | NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; 22 | NSTimeInterval timeSinceLastClick = currentTime - lastClickTime; 23 | 24 | NSLog(@"[XhsPlus] 当前时间: %f, 上次点击时间: %f", currentTime, lastClickTime); 25 | if (timeSinceLastClick < 0.5) { 26 | NSLog(@"[XhsPlus] 检测到双击"); 27 | // 在调用 showSettings 时,附加当前的暗黑模式状态作为参数 28 | [XhsSettingsViewController showSettings]; 29 | } else { 30 | NSLog(@"[XhsPlus] 检测到单击"); 31 | lastClickTime = currentTime; 32 | %orig; 33 | return; 34 | } 35 | } 36 | 37 | %end 38 | 39 | @interface XYTabBar : UIView 40 | @property (nonatomic, copy) NSArray *tabs; 41 | @end 42 | 43 | %hook XYTabBar 44 | 45 | - (void)layoutSubviews { 46 | %orig; 47 | 48 | if (self.subviews.count >= 3) { 49 | BOOL removeShoppingTab = [[NSUserDefaults standardUserDefaults] boolForKey:@"remove_tab_shopping"]; 50 | BOOL removePostTab = [[NSUserDefaults standardUserDefaults] boolForKey:@"remove_tab_post"]; 51 | 52 | if (removeShoppingTab && removePostTab) { 53 | [[self.subviews objectAtIndex:1] removeFromSuperview]; 54 | [[self.subviews objectAtIndex:1] removeFromSuperview]; 55 | } else { 56 | if (removeShoppingTab) { 57 | [[self.subviews objectAtIndex:1] removeFromSuperview]; 58 | } 59 | if (removePostTab) { 60 | [[self.subviews objectAtIndex:2] removeFromSuperview]; 61 | } 62 | } 63 | } 64 | 65 | CGFloat tabWidth = CGRectGetWidth(self.bounds) / self.subviews.count; 66 | CGFloat xPosition = 0; 67 | 68 | for (UIView *subview in self.subviews) { 69 | CGRect frame = subview.frame; 70 | frame.origin.x = xPosition; 71 | frame.size.width = tabWidth; 72 | subview.frame = frame; 73 | 74 | xPosition += tabWidth; 75 | } 76 | } 77 | 78 | %end 79 | 80 | %hook XYPHMediaSaveConfig 81 | 82 | - (void)setDisableWatermark:(_Bool)arg1 { 83 | BOOL removeWatermark = [[NSUserDefaults standardUserDefaults] boolForKey:@"remove_save_watermark"]; 84 | %orig(removeWatermark); 85 | } 86 | 87 | - (void)setDisableSave:(_Bool)arg1 { 88 | BOOL forceSaveMedia = [[NSUserDefaults standardUserDefaults] boolForKey:@"force_save_media"]; 89 | if (forceSaveMedia) { 90 | %orig(NO); 91 | } else { 92 | %orig(arg1); 93 | } 94 | } 95 | %end -------------------------------------------------------------------------------- /XhsSettingsViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface XhsSettingsViewController : UIViewController 4 | 5 | + (void)showSettings; 6 | 7 | @end -------------------------------------------------------------------------------- /XhsSettingsViewController.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface XhsSettingsViewController : UIViewController 4 | 5 | + (void)showSettings; 6 | 7 | @end 8 | 9 | @implementation XhsSettingsViewController { 10 | UITableView *_tableView; 11 | NSArray *options; 12 | } 13 | 14 | static XhsSettingsViewController *instance = nil; 15 | 16 | + (void)showSettings { 17 | if (instance == nil) { 18 | instance = [[XhsSettingsViewController alloc] init]; 19 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:instance]; 20 | navigationController.modalPresentationStyle = UIModalPresentationOverFullScreen; 21 | 22 | if (@available(iOS 13.0, *)) { 23 | // For iOS 13 and later 24 | UIWindowScene *windowScene = (UIWindowScene *)UIApplication.sharedApplication.connectedScenes.allObjects.firstObject; 25 | UIWindow *mainWindow = windowScene.windows.firstObject; 26 | if (mainWindow) { 27 | UIViewController *rootViewController = mainWindow.rootViewController; 28 | [rootViewController presentViewController:navigationController animated:YES completion:nil]; 29 | } 30 | } else { 31 | // For iOS 12 and earlier 32 | UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window]; 33 | if (mainWindow) { 34 | UIViewController *rootViewController = mainWindow.rootViewController; 35 | [rootViewController presentViewController:navigationController animated:YES completion:nil]; 36 | } 37 | } 38 | } 39 | } 40 | 41 | 42 | - (void)viewDidLoad { 43 | [super viewDidLoad]; 44 | [self setupUI]; 45 | [self setupOptions]; 46 | 47 | // 设置导航控制器的代理 48 | self.navigationController.interactivePopGestureRecognizer.delegate = (id)self; 49 | 50 | // 添加左侧滑动手势 51 | UIScreenEdgePanGestureRecognizer *gestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeGesture:)]; 52 | gestureRecognizer.edges = UIRectEdgeLeft; 53 | [self.view addGestureRecognizer:gestureRecognizer]; 54 | } 55 | 56 | - (void)dismissSettingsAndResetInstance { 57 | [self dismissViewControllerAnimated:YES completion:^{ 58 | instance = nil; 59 | }]; 60 | } 61 | 62 | - (void)handleSwipeGesture:(UIScreenEdgePanGestureRecognizer *)gestureRecognizer { 63 | if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { 64 | // 手势开始,返回上一页 65 | [self dismissSettingsAndResetInstance]; 66 | } 67 | } 68 | 69 | - (void)setupOptions { 70 | options = @[ 71 | @{@"title": @"去除底栏购物", @"key": @"remove_tab_shopping"}, 72 | @{@"title": @"去除底栏加号", @"key": @"remove_tab_post"}, 73 | @{@"title": @"去除保存水印", @"key": @"remove_save_watermark"}, 74 | @{@"title": @"强制保存媒体", @"key": @"force_save_media"} 75 | ]; 76 | } 77 | 78 | - (void)setupUI { 79 | if (@available(iOS 13.0, *)) { 80 | self.view.backgroundColor = [UIColor systemBackgroundColor]; 81 | } else { 82 | self.view.backgroundColor = [UIColor whiteColor]; 83 | } 84 | 85 | self.navigationItem.title = @"小红书+"; 86 | 87 | UIBarButtonItem *leftButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"结束进程" style:UIBarButtonItemStylePlain target:self action:@selector(killProcess)]; 88 | [leftButtonItem setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor redColor]} forState:UIControlStateNormal]; 89 | self.navigationItem.leftBarButtonItem = leftButtonItem; 90 | 91 | UIBarButtonItem *rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"关闭窗口" style:UIBarButtonItemStylePlain target:self action:@selector(dismissSettings)]; 92 | self.navigationItem.rightBarButtonItem = rightButtonItem; 93 | 94 | [self setupTableView]; 95 | } 96 | 97 | - (void)setupTableView { 98 | _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; 99 | _tableView.dataSource = self; 100 | _tableView.delegate = self; 101 | _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 102 | [self.view addSubview:_tableView]; 103 | } 104 | 105 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 106 | return 2; // 设置和关于两个部分 107 | } 108 | 109 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 110 | if (section == 0) { 111 | return options.count; 112 | } else { 113 | return 3; 114 | } 115 | } 116 | 117 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 118 | UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 119 | 120 | if (indexPath.section == 0) { 121 | NSDictionary *option = options[indexPath.row]; 122 | cell.textLabel.text = option[@"title"]; 123 | 124 | // 创建一个透明的 UIButton 用于拦截点击事件 125 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 126 | button.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height); 127 | [button addTarget:self action:@selector(optionTapped:) forControlEvents:UIControlEventTouchUpInside]; 128 | [cell.contentView addSubview:button]; 129 | 130 | UISwitch *switchView = [[UISwitch alloc] init]; 131 | switchView.on = [[NSUserDefaults standardUserDefaults] boolForKey:option[@"key"]]; 132 | [switchView addTarget:self action:@selector(switchValueChanged:) forControlEvents:UIControlEventValueChanged]; 133 | cell.accessoryView = switchView; 134 | } else { 135 | if (indexPath.row == 0) { 136 | // 版本 137 | cell.textLabel.text = @"小红书+ v0.0.1 @维他入我心"; 138 | cell.userInteractionEnabled = NO; 139 | } else if (indexPath.row == 1) { 140 | // GitHub 141 | cell.textLabel.text = @"GitHub 开源地址"; 142 | } else if (indexPath.row == 2) { 143 | // Telegram 144 | cell.textLabel.text = @"Telegram 反馈地址"; 145 | } 146 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 147 | } 148 | 149 | return cell; 150 | } 151 | 152 | - (void)optionTapped:(UIButton *)sender { 153 | // 拦截了点击事件,什么也不做 154 | } 155 | 156 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 157 | if (section == 0) { 158 | return @"设置"; 159 | } else if (section == 1) { 160 | return @"关于"; 161 | } else { 162 | return nil; 163 | } 164 | } 165 | 166 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 167 | [_tableView deselectRowAtIndexPath:indexPath animated:YES]; 168 | if (indexPath.section == 1) { 169 | if (indexPath.row == 1) { 170 | // GitHub 171 | [self openGitHub]; 172 | } else if (indexPath.row == 2) { 173 | // Telegram 174 | [self openTelegram]; 175 | } 176 | } 177 | } 178 | 179 | - (void)switchValueChanged:(UISwitch *)sender { 180 | UITableViewCell *cell = (UITableViewCell *)sender.superview; 181 | NSIndexPath *indexPath = [_tableView indexPathForCell:cell]; 182 | NSDictionary *option = options[indexPath.row]; 183 | [[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:option[@"key"]]; 184 | } 185 | 186 | - (void)openGitHub { 187 | NSString *githubURLString = @"https://github.com/Wtrwx/XhsPlus"; 188 | NSURL *githubURL = [NSURL URLWithString:githubURLString]; 189 | if ([[UIApplication sharedApplication] canOpenURL:githubURL]) { 190 | [[UIApplication sharedApplication] openURL:githubURL options:@{} completionHandler:nil]; 191 | } 192 | } 193 | 194 | - (void)openTelegram { 195 | NSString *telegramURLString = @"https://t.me/wtrwx"; 196 | NSURL *telegramURL = [NSURL URLWithString:telegramURLString]; 197 | if ([[UIApplication sharedApplication] canOpenURL:telegramURL]) { 198 | [[UIApplication sharedApplication] openURL:telegramURL options:@{} completionHandler:nil]; 199 | } 200 | } 201 | 202 | - (void)killProcess { 203 | NSLog(@"[XhsPlus] killProcess"); 204 | exit(0); 205 | } 206 | 207 | - (void)dismissSettings { 208 | [self dismissViewControllerAnimated:YES completion:^{ 209 | instance = nil; 210 | }]; 211 | } 212 | 213 | @end 214 | 215 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.wtrwx.xhsplus 2 | Name: XhsPlus 3 | Version: 0.0.1 4 | Architecture: iphoneos-arm 5 | Description: A tweak for Xhs 6 | Maintainer: vita 7 | Author: vita 8 | Section: Tweaks 9 | Depends: mobilesubstrate (>= 0.9.5000) 10 | --------------------------------------------------------------------------------