From 5282d671038214ef1faf3c032eb8e1eddd4e94af Mon Sep 17 00:00:00 2001 From: faroukbmiled Date: Sun, 5 Apr 2026 03:51:09 +0100 Subject: [PATCH] feat: Unlock password-locked reels (auto unlock and view passowrd) --- README.md | 1 + src/Features/Reels/PasswordedReels.xm | 189 ++++++++++++++++++++++++++ src/InstagramHeaders.h | 6 + src/Settings/TweakSettings.m | 1 + src/Tweak.x | 3 +- 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/Features/Reels/PasswordedReels.xm diff --git a/README.md b/README.md index 7846554..a6cd8d5 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ A feature-rich iOS tweak for Instagram, forked from [SCInsta](https://github.com - Always show progress scrubber - Disable auto-unmuting reels (properly blocks mute switch, volume buttons, and announcer broadcasts) **\*** - Confirm reel refresh +- Unlock password-locked reels **\*** - Hide reels header - Hide reels blend button - Disable scrolling reels diff --git a/src/Features/Reels/PasswordedReels.xm b/src/Features/Reels/PasswordedReels.xm new file mode 100644 index 0000000..73400d3 --- /dev/null +++ b/src/Features/Reels/PasswordedReels.xm @@ -0,0 +1,189 @@ +#import "../../InstagramHeaders.h" +#import "../../Utils.h" +#import +#import + +// Password-locked reels use IGMediaOverlayProfileWithPasswordView as a blur overlay. +// The password is stored in the _asnwer ivar (IG typo). We read it at runtime, +// then provide buttons to auto-fill + submit or reveal + copy the password. + +#define SCI_PW_BTN_TAG 1342 + +static NSString * _Nullable sciGetPassword(id overlayView) { + Class cls = [overlayView class]; + while (cls && cls != [UIView class]) { + unsigned int count = 0; + Ivar *ivars = class_copyIvarList(cls, &count); + for (unsigned int i = 0; i < count; i++) { + const char *name = ivar_getName(ivars[i]); + if (name && strcmp(name, "_asnwer") == 0) { + id value = object_getIvar(overlayView, ivars[i]); + free(ivars); + if ([value isKindOfClass:[NSString class]] && [(NSString *)value length] > 0) + return (NSString *)value; + return nil; + } + } + if (ivars) free(ivars); + cls = class_getSuperclass(cls); + } + + // Fallback: scan for any password-related string ivar + cls = [overlayView class]; + while (cls && cls != [UIView class]) { + unsigned int count = 0; + Ivar *ivars = class_copyIvarList(cls, &count); + for (unsigned int i = 0; i < count; i++) { + const char *type = ivar_getTypeEncoding(ivars[i]); + if (!type || type[0] != '@') continue; + @try { + id value = object_getIvar(overlayView, ivars[i]); + if (![value isKindOfClass:[NSString class]] || [(NSString *)value length] == 0) continue; + const char *name = ivar_getName(ivars[i]); + if (!name) continue; + NSString *lower = [[NSString stringWithUTF8String:name] lowercaseString]; + if ([lower containsString:@"answer"] || [lower containsString:@"asnwer"] || + [lower containsString:@"password"] || [lower containsString:@"secret"]) { + free(ivars); + return (NSString *)value; + } + } @catch(id e) {} + } + if (ivars) free(ivars); + cls = class_getSuperclass(cls); + } + return nil; +} + +static UITextField * _Nullable sciFindTextField(UIView *root) { + NSMutableArray *stack = [NSMutableArray arrayWithObject:root]; + while (stack.count > 0) { + UIView *v = stack.lastObject; + [stack removeLastObject]; + if ([v isKindOfClass:[UITextField class]]) return (UITextField *)v; + for (UIView *sub in v.subviews) [stack addObject:sub]; + } + return nil; +} + +static UIView * _Nullable sciFindSubmitButton(UIView *root) { + NSMutableArray *stack = [NSMutableArray arrayWithObject:root]; + while (stack.count > 0) { + UIView *v = stack.lastObject; + [stack removeLastObject]; + if ([NSStringFromClass([v class]) containsString:@"IGDSMediaTextButton"]) return v; + for (UIView *sub in v.subviews) [stack addObject:sub]; + } + return nil; +} + +%hook IGMediaOverlayProfileWithPasswordView + +- (void)didMoveToSuperview { + %orig; + if (!self.superview) return; + if (![SCIUtils getBoolPref:@"unlock_password_reels"]) return; + [self sciAddButtons]; +} + +- (void)layoutSubviews { + %orig; + if (![SCIUtils getBoolPref:@"unlock_password_reels"]) return; + [self sciAddButtons]; +} + +%new - (void)sciAddButtons { + if ([self viewWithTag:SCI_PW_BTN_TAG]) return; + + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:18 weight:UIImageSymbolWeightBold]; + + UIButton *unlockBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + unlockBtn.tag = SCI_PW_BTN_TAG; + [unlockBtn setImage:[UIImage systemImageNamed:@"lock.open.fill" withConfiguration:config] forState:UIControlStateNormal]; + unlockBtn.tintColor = [UIColor colorWithRed:1.0 green:0.85 blue:0.0 alpha:1.0]; + unlockBtn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5]; + unlockBtn.layer.cornerRadius = 20; + unlockBtn.translatesAutoresizingMaskIntoConstraints = NO; + [unlockBtn addTarget:self action:@selector(sciUnlockTapped) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:unlockBtn]; + + UIButton *eyeBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + eyeBtn.tag = SCI_PW_BTN_TAG + 1; + [eyeBtn setImage:[UIImage systemImageNamed:@"eye.fill" withConfiguration:config] forState:UIControlStateNormal]; + eyeBtn.tintColor = [UIColor whiteColor]; + eyeBtn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5]; + eyeBtn.layer.cornerRadius = 20; + eyeBtn.translatesAutoresizingMaskIntoConstraints = NO; + [eyeBtn addTarget:self action:@selector(sciShowPasswordTapped) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:eyeBtn]; + + [NSLayoutConstraint activateConstraints:@[ + [unlockBtn.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor constant:200], + [unlockBtn.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-16], + [unlockBtn.widthAnchor constraintEqualToConstant:40], + [unlockBtn.heightAnchor constraintEqualToConstant:40], + + [eyeBtn.topAnchor constraintEqualToAnchor:unlockBtn.bottomAnchor constant:12], + [eyeBtn.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-16], + [eyeBtn.widthAnchor constraintEqualToConstant:40], + [eyeBtn.heightAnchor constraintEqualToConstant:40], + ]]; +} + +%new - (void)sciUnlockTapped { + UIImpactFeedbackGenerator *haptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; + [haptic impactOccurred]; + + NSString *password = sciGetPassword(self); + if (!password) { + [SCIUtils showErrorHUDWithDescription:@"No password found"]; + return; + } + + UITextField *textField = sciFindTextField(self); + if (!textField) { + [SCIUtils showErrorHUDWithDescription:@"No text field found"]; + return; + } + + textField.text = password; + [textField sendActionsForControlEvents:UIControlEventEditingChanged]; + [[NSNotificationCenter defaultCenter] postNotificationName:UITextFieldTextDidChangeNotification object:textField]; + + if (textField.delegate) { + if ([textField.delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) + [textField.delegate textField:textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:password]; + if ([textField.delegate respondsToSelector:@selector(textFieldDidChangeSelection:)]) + [textField.delegate textFieldDidChangeSelection:textField]; + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + UIView *submitBtn = sciFindSubmitButton(self); + if (submitBtn && [submitBtn isKindOfClass:[UIControl class]]) { + [(UIControl *)submitBtn setHidden:NO]; + [(UIControl *)submitBtn sendActionsForControlEvents:UIControlEventTouchUpInside]; + } + }); +} + +%new - (void)sciShowPasswordTapped { + UIImpactFeedbackGenerator *haptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + [haptic impactOccurred]; + + NSString *password = sciGetPassword(self); + if (!password) { + [SCIUtils showErrorHUDWithDescription:@"No password found"]; + return; + } + + [[UIPasteboard generalPasteboard] setString:password]; + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Password" + message:password + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Copied!" style:UIAlertActionStyleCancel handler:nil]]; + UIViewController *topVC = topMostController(); + if (topVC) [topVC presentViewController:alert animated:YES completion:nil]; +} + +%end diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index bf5907f..fbcff91 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -425,6 +425,12 @@ @interface IGSundialViewerNavigationBarOld : UIView @end +@interface IGMediaOverlayProfileWithPasswordView : UIView +- (void)sciAddButtons; +- (void)sciUnlockTapped; +- (void)sciShowPasswordTapped; +@end + @interface IGUFIInteractionCountsView : UIView @end diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index 3a5ac1f..d4379ef 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -85,6 +85,7 @@ [SCISetting switchCellWithTitle:@"Always show progress scrubber" subtitle:@"Forces the progress bar to appear on every reel" defaultsKey:@"reels_show_scrubber"], [SCISetting switchCellWithTitle:@"Disable auto-unmuting reels" subtitle:@"Prevents reels from unmuting when the volume/silent button is pressed" defaultsKey:@"disable_auto_unmuting_reels" requiresRestart:YES], [SCISetting switchCellWithTitle:@"Confirm reel refresh" subtitle:@"Shows an alert when you trigger a reels refresh" defaultsKey:@"refresh_reel_confirm"], + [SCISetting switchCellWithTitle:@"Unlock password-locked reels" subtitle:@"Shows buttons to reveal and auto-fill the password on locked reels" defaultsKey:@"unlock_password_reels"], ] }, @{ diff --git a/src/Tweak.x b/src/Tweak.x index 226b92c..7406ab0 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -46,7 +46,8 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; @"disable_auto_unmuting_reels": @(YES), @"doom_scrolling_reel_count": @(1), @"no_seen_visual": @(YES), - @"send_audio_as_file": @(YES) + @"send_audio_as_file": @(YES), + @"unlock_password_reels": @(YES) }; [[NSUserDefaults standardUserDefaults] registerDefaults:sciDefaults];