From 16a9a5e3160c1d34d5d0e624840d0739508bb708 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Sun, 1 Mar 2026 17:35:41 -0800 Subject: [PATCH 01/16] chore: bump dev version --- README.md | 2 +- control | 2 +- src/Tweak.x | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 287f1176..30ff57f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SCInsta A feature-rich tweak for Instagram on iOS!\ -`Version v1.1.1` | `Tested on Instagram 418.2.0` +`Version v1.2.0-dev` | `Tested on Instagram 418.2.0` --- diff --git a/control b/control index f1c99cde..5de99b48 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: com.socuul.scinsta Name: SCInsta -Version: 1.1.1 +Version: 1.2.0 Architecture: iphoneos-arm Description: A feature-rich tweak for Instagram on iOS! Homepage: https://github.com/SoCuul/SCInsta diff --git a/src/Tweak.x b/src/Tweak.x index b81a98f3..5e08b62d 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -13,7 +13,7 @@ /////////////////////////////////////////////////////////// // * Tweak version * -NSString *SCIVersionString = @"v1.1.1"; +NSString *SCIVersionString = @"v1.2.0-dev"; // Variables that work across features BOOL dmVisualMsgsViewedButtonEnabled = false; From 651a6c44f387c8c44cad956d90c34fd047a09983 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Sun, 1 Mar 2026 17:35:49 -0800 Subject: [PATCH 02/16] feat: disable app haptics --- README.md | 1 + src/Features/General/DisableHaptics.x | 33 +++++++++++++++++++++++++++ src/Settings/TweakSettings.m | 3 ++- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/Features/General/DisableHaptics.x diff --git a/README.md b/README.md index 30ff57f1..01eaeb47 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ A feature-rich tweak for Instagram on iOS!\ - Use detailed (native) color picker - Enable liquid glass buttons - Enable teen app icons +- Disable app haptics - IG Notes: - Hide notes tray - Hide friends map diff --git a/src/Features/General/DisableHaptics.x b/src/Features/General/DisableHaptics.x new file mode 100644 index 00000000..ad59c934 --- /dev/null +++ b/src/Features/General/DisableHaptics.x @@ -0,0 +1,33 @@ +#import "../../Utils.h" + +%hook UIImpactFeedbackGenerator +- (void)impactOccurred { + if (![SCIUtils getBoolPref:@"disable_haptics"]) %orig; +} +- (void)impactOccurredWithIntensity:(CGFloat)intensity { + if (![SCIUtils getBoolPref:@"disable_haptics"]) %orig(intensity); +} +%end + +%hook UINotificationFeedbackGenerator +- (void)notificationOccurred:(UINotificationFeedbackType)notificationType { + if (![SCIUtils getBoolPref:@"disable_haptics"]) %orig(notificationType); +} +%end + +%hook UISelectionFeedbackGenerator +- (void)selectionChanged { + if (![SCIUtils getBoolPref:@"disable_haptics"]) %orig; +} +%end + +%hook CHHapticEngine +- (BOOL)startAndReturnError:(NSError **)outError { + if (![SCIUtils getBoolPref:@"disable_haptics"]) { + return %orig(outError); + } + else { + return NO; + } +} +%end \ No newline at end of file diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index 15338c66..9484c0f3 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -37,7 +37,8 @@ + (NSArray *)sections { [SCISetting switchCellWithTitle:@"Use detailed color picker" subtitle:@"Long press on the eyedropper tool in stories to customize the text color more precisely" defaultsKey:@"detailed_color_picker"], [SCISetting switchCellWithTitle:@"Enable liquid glass buttons" subtitle:@"Enables experimental liquid glass buttons within the app" defaultsKey:@"liquid_glass_buttons" requiresRestart:YES], [SCISetting switchCellWithTitle:@"Enable liquid glass surfaces" subtitle:@"Enables liquid glass for other elements, such as menus" defaultsKey:@"liquid_glass_surfaces" requiresRestart:YES], - [SCISetting switchCellWithTitle:@"Enable teen app icons" subtitle:@"When enabled, hold down on the Instagram logo to change the app icon" defaultsKey:@"teen_app_icons" requiresRestart:YES] + [SCISetting switchCellWithTitle:@"Enable teen app icons" subtitle:@"When enabled, hold down on the Instagram logo to change the app icon" defaultsKey:@"teen_app_icons" requiresRestart:YES], + [SCISetting switchCellWithTitle:@"Disable app haptics" subtitle:@"Disables haptics/vibrations within the Instagram app" defaultsKey:@"disable_haptics"] ] }, @{ From 797d8042d59f596c3a6feb18ae3799d5c1abe164 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Mon, 2 Mar 2026 18:37:01 -0800 Subject: [PATCH 03/16] feat: hide metrics --- README.md | 1 + src/Features/General/HideMetrics.x | 25 +++++++++++++++++++++++++ src/Settings/TweakSettings.m | 1 + 3 files changed, 27 insertions(+) create mode 100644 src/Features/General/HideMetrics.x diff --git a/README.md b/README.md index 01eaeb47..fba0b33f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ A feature-rich tweak for Instagram on iOS!\ - No suggested chats - Hide trending searches - Hide explore posts grid + - Hide metrics ### Feed - Hide stories tray diff --git a/src/Features/General/HideMetrics.x b/src/Features/General/HideMetrics.x new file mode 100644 index 00000000..5aba28e1 --- /dev/null +++ b/src/Features/General/HideMetrics.x @@ -0,0 +1,25 @@ +#import "../../Utils.h" + +%hook IGSundialViewerVerticalUFI +- (void)setNumLikes:(NSInteger)num { + return %orig([SCIUtils getBoolPref:@"hide_metrics"] ? 0 : num); +} +- (void)setNumReshares:(NSInteger)num { + return %orig([SCIUtils getBoolPref:@"hide_metrics"] ? 0 : num); +} +- (void)setNumComments:(NSInteger)num { + return %orig([SCIUtils getBoolPref:@"hide_metrics"] ? 0 : num); +} +- (void)setNumReposts:(NSInteger)num { + return %orig([SCIUtils getBoolPref:@"hide_metrics"] ? 0 : num); +} +- (void)setNumSaves:(NSInteger)num { + return %orig([SCIUtils getBoolPref:@"hide_metrics"] ? 0 : num); +} +%end + +%hook IGUFIButtonWithCountsView +- (void)setCountString:(id)string showButton:(BOOL)showButton { + return %orig([SCIUtils getBoolPref:@"hide_metrics"] ? @"" : string, showButton); +} +%end \ No newline at end of file diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index 9484c0f3..e61f2b18 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -57,6 +57,7 @@ + (NSArray *)sections { [SCISetting switchCellWithTitle:@"No suggested chats" subtitle:@"Hides the suggested broadcast channels in direct messages" defaultsKey:@"no_suggested_chats"], [SCISetting switchCellWithTitle:@"Hide explore posts grid" subtitle:@"Hides the grid of suggested posts on the explore/search tab" defaultsKey:@"hide_explore_grid"], [SCISetting switchCellWithTitle:@"Hide trending searches" subtitle:@"Hides the trending searches under the explore search bar" defaultsKey:@"hide_trending_searches"], + [SCISetting switchCellWithTitle:@"Hide metrics" subtitle:@"Hides the metrics numbers under posts & reels (likes, comments, reshares, shares)" defaultsKey:@"hide_metrics"], ] }] ], From a8ba25076df82323e1b771e372944e474c5efda3 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Mon, 2 Mar 2026 20:08:32 -0800 Subject: [PATCH 04/16] feat: prevent ai search carousel from displacing comments --- src/Features/General/HideMetaAI.xm | 18 ++++++++++++++++++ src/InstagramHeaders.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/Features/General/HideMetaAI.xm b/src/Features/General/HideMetaAI.xm index 1134ca99..195214a0 100644 --- a/src/Features/General/HideMetaAI.xm +++ b/src/Features/General/HideMetaAI.xm @@ -346,6 +346,24 @@ // Reels/Sundial // Suggested AI searches in comment section +%hook IGCommentConfig +- (id)initWithUserSession:(id)session + commentThreadConfiguration:(IGCommentThreadConfiguration *)threadConfig +sponsoredSupportConfiguration:(id)supportConfig + CTAPresenterContext:(id)context + replyText:(id)text + loggingDelegate:(id)loggingDelegate + presentingViewController:(id)vc + childCommentThreadDelegate:(id)threadDelegate +{ + if ([SCIUtils getBoolPref:@"hide_meta_ai"]) { + [threadConfig setValue:@(YES) forKey:@"disableMetaAICarousel"]; + } + return %orig(session, threadConfig, supportConfig, context, text, loggingDelegate, vc, threadDelegate); +} +%end + +// Suggested AI searches in comment section (workaround if setting comment thread config fails) %hook IGCommentThreadAICarousel - (id)initWithLauncherSet:(id)arg1 hasSearchPrefix:(BOOL)arg2 { if ([SCIUtils getBoolPref:@"hide_meta_ai"]) { diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index d66f1fd0..aec9aee0 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -486,6 +486,9 @@ @property (readonly, nonatomic) IGCreationActionBarButton *button; @end +@interface IGCommentThreadConfiguration : NSObject +@end + ///////////////////////////////////////////////////////////////////////////// From 19afe7354b710acbbc11615807a76c6d2b304165 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Tue, 10 Mar 2026 21:43:06 -0700 Subject: [PATCH 05/16] fix: hide ai images entrypoint during story creation --- src/Features/General/HideMetaAI.xm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/General/HideMetaAI.xm b/src/Features/General/HideMetaAI.xm index 195214a0..8dbdce0c 100644 --- a/src/Features/General/HideMetaAI.xm +++ b/src/Features/General/HideMetaAI.xm @@ -401,7 +401,7 @@ sponsoredSupportConfiguration:(id)supportConfig NSLog(@"[SCInsta] Hiding meta ai: ai images add to story suggestion"); if ([SCIUtils getBoolPref:@"hide_meta_ai"]) { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", @[ @(10), @(11) ]]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", @[ @(9), @(10), @(11) ]]; newTools = [tools filteredArrayUsingPredicate:predicate]; } From 19d21e238c265b264c488448dd6eb490732b06c5 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Tue, 10 Mar 2026 21:42:30 -0700 Subject: [PATCH 06/16] fix: keep deleted messages on new app versions --- .../StoriesAndMessages/KeepDeletedMessages.x | 60 ++++++++++++++++++- src/InstagramHeaders.h | 15 +++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/Features/StoriesAndMessages/KeepDeletedMessages.x b/src/Features/StoriesAndMessages/KeepDeletedMessages.x index 014b66d3..40097a79 100644 --- a/src/Features/StoriesAndMessages/KeepDeletedMessages.x +++ b/src/Features/StoriesAndMessages/KeepDeletedMessages.x @@ -1,6 +1,64 @@ #import "../../Utils.h" #import "../../InstagramHeaders.h" +// Modern Instagram versions +%hook IGDirectRealtimeIrisDeltaHandler +- (void)handleIrisDeltas:(NSArray *)deltas { + if (![SCIUtils getBoolPref:@"keep_deleted_message"]) { + %orig(deltas); + return; + } + + NSArray *originalDeltas = deltas; + NSMutableArray *filteredDeltas = [NSMutableArray arrayWithCapacity:[originalDeltas count]]; + + for (IGDirectRealtimeIrisDelta *delta in originalDeltas) { + if (![delta isKindOfClass:%c(IGDirectRealtimeIrisDelta)]) continue; + + // irisDeltaPayload + IGDirectRealtimeIrisDeltaPayload *irisDeltaPayload = [delta valueForKey:@"payload"]; + if (![irisDeltaPayload isKindOfClass:%c(IGDirectRealtimeIrisDeltaPayload)]) continue; + + // threadDeltaPayload + IGDirectRealtimeIrisThreadDeltaPayload *threadDeltaPayload = [irisDeltaPayload valueForKey:@"threadDeltaPayload"]; + if (![threadDeltaPayload isKindOfClass:%c(IGDirectRealtimeIrisThreadDeltaPayload)]) continue; + + // threadDelta + IGDirectRealtimeIrisThreadDelta *threadDelta = [threadDeltaPayload valueForKey:@"threadDelta"]; + if (![threadDelta isKindOfClass:%c(IGDirectRealtimeIrisThreadDelta)]) continue; + + // removeItem_messageId + NSString *removeItem_messageId = [threadDelta valueForKey:@"removeItem_messageId"]; + if (removeItem_messageId) continue; + + // * This general concept works for *replacing* the message contents instead of deleting it + // * Not sure how to get the existing message content, to add it to the edit history though... + // * It isn't very helpful to just replace the message contents, so this is still WIP + /* NSString *messageId = [threadDelta valueForKey:@"removeItem_messageId"]; + if (messageId) { + // SCILog(@"idk"); + IGDirectMessageContentMutation *mutation = [%c(IGDirectMessageContentMutation) new]; + [mutation setValue:@(3) forKey:@"subtype"]; + [mutation setValue:@"[deleted]" forKey:@"editText_newContent"]; + [mutation setValue:@(1) forKey:@"editText_editCount"]; // update to include old edit count + 1 + //[mutation setValue:@[] forKey:@"editText_editHistory"]; // update to include old edit history + current content + + // Modify thread delta to update message with new contents + [threadDelta setValue:@(3) forKey:@"subtype"]; + [threadDelta setValue:messageId forKey:@"mutateItem_messageId"]; + [threadDelta setValue:mutation forKey:@"mutateItem_messageContentMutation"]; + [threadDelta setValue:nil forKey:@"removeItem_messageId"]; + } + */ + + [filteredDeltas addObject:delta]; + } + + %orig([filteredDeltas copy]); +} +%end + +// Legacy versions fallback (just in case) %hook IGDirectRealtimeIrisThreadDelta + (id)removeItemWithMessageId:(id)arg1 { if ([SCIUtils getBoolPref:@"keep_deleted_message"]) { @@ -12,7 +70,7 @@ %end %hook IGDirectMessageUpdate -+ (id)removeMessageWithMessageId:(id)arg1{ ++ (id)removeMessageWithMessageId:(id)arg1 { if ([SCIUtils getBoolPref:@"keep_deleted_message"]) { arg1 = NULL; } diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index aec9aee0..4556355a 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -489,6 +489,21 @@ @interface IGCommentThreadConfiguration : NSObject @end +@interface IGDirectRealtimeIrisDelta : NSObject +@end + +@interface IGDirectRealtimeIrisDeltaPayload : NSObject +@end + +@interface IGDirectRealtimeIrisThreadDeltaPayload : NSObject +@end + +@interface IGDirectRealtimeIrisThreadDelta : NSObject +@end + +@interface IGDirectMessageContentMutation : NSObject +@end + ///////////////////////////////////////////////////////////////////////////// From f499b70040ada0917664c4983804390acb70e100 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Thu, 19 Mar 2026 03:18:39 -0700 Subject: [PATCH 07/16] feat: improve suggested users/chats in dm search --- src/Features/General/NoSuggestedUsers.x | 2 +- src/InstagramHeaders.h | 2 - src/Tweak.x | 61 ++++++++++++++----------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/Features/General/NoSuggestedUsers.x b/src/Features/General/NoSuggestedUsers.x index f09facf1..50a0c613 100644 --- a/src/Features/General/NoSuggestedUsers.x +++ b/src/Features/General/NoSuggestedUsers.x @@ -64,7 +64,7 @@ // Section header if ([obj isKindOfClass:%c(IGLabelItemViewModel)]) { // Suggested for you - if ([[obj labelTitle] isEqualToString:@"Suggested for you"]) { + if ([[obj valueForKey:@"tag"] intValue] == 2) { // 2 == Suggested Users if ([SCIUtils getBoolPref:@"no_suggested_users"]) { NSLog(@"[SCInsta] Hiding suggested users (header: activity feed)"); diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index 4556355a..23adcdf8 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -251,8 +251,6 @@ @end @interface IGLabelItemViewModel : NSObject -- (id)labelTitle; -- (id)uniqueIdentifier; @end @interface IGDirectInboxSuggestedThreadCellViewModel : NSObject diff --git a/src/Tweak.x b/src/Tweak.x index 5e08b62d..a99654ca 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -193,6 +193,37 @@ shouldPersistLastBugReportId:(id)arg6 // Hide items // Direct suggested chats (in search bar) +BOOL showSearchSectionLabelForTag(NSInteger tag) { + if ( + (tag == 18 && [SCIUtils getBoolPref:@"hide_meta_ai"]) // AI + || (tag == 20 && [SCIUtils getBoolPref:@"hide_meta_ai"]) // Ask Meta AI + || (tag == 2 && [SCIUtils getBoolPref:@"no_suggested_users"]) // More suggestions + || (tag == 13 && [SCIUtils getBoolPref:@"no_suggested_chats"]) // Suggested channels + ) { + return false; + } + + return true; +} + +%hook IGDirectInboxSearchSectionPartitioningComponent +- (id)initWithSectionTitle:(id)arg1 + maxRecipients:(NSInteger)maxRecipients + filterBlock:(id)arg3 + comparator:(id)arg4 + expandedSections:(id)arg5 + type:(NSInteger)arg6 + recipientListSectionType:(NSInteger)tag +{ + if (showSearchSectionLabelForTag(tag)) { + return %orig(arg1, maxRecipients, arg3, arg4, arg5, arg6, tag); + } + else { + return %orig(arg1, 0, arg3, arg4, arg5, arg6, tag); + } +} +%end + %hook IGDirectInboxSearchListAdapterDataSource - (id)objectsForListAdapter:(id)arg1 { NSArray *originalObjs = %orig(); @@ -201,34 +232,12 @@ shouldPersistLastBugReportId:(id)arg6 for (id obj in originalObjs) { BOOL shouldHide = NO; - // Section header + // Section headers if ([obj isKindOfClass:%c(IGLabelItemViewModel)]) { - // Broadcast channels - if ([[obj valueForKey:@"uniqueIdentifier"] isEqualToString:@"channels"]) { - if ([SCIUtils getBoolPref:@"no_suggested_chats"]) { - NSLog(@"[SCInsta] Hiding suggested chats (header)"); - - shouldHide = YES; - } - } - - // Ask Meta AI - else if ([[obj valueForKey:@"labelTitle"] isEqualToString:@"Ask Meta AI"]) { - if ([SCIUtils getBoolPref:@"hide_meta_ai"]) { - NSLog(@"[SCInsta] Hiding meta ai suggested chats (header)"); - - shouldHide = YES; - } - } - - // AI - else if ([[obj valueForKey:@"labelTitle"] isEqualToString:@"AI"]) { - if ([SCIUtils getBoolPref:@"hide_meta_ai"]) { - NSLog(@"[SCInsta] Hiding ai suggested chats (header)"); - - shouldHide = YES; - } + NSNumber *tag = [obj valueForKey:@"tag"]; + if (tag && !showSearchSectionLabelForTag([tag intValue])) { + shouldHide = YES; } } From e20953595bc254913fda55b0efad880ce0954374 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Thu, 19 Mar 2026 03:19:16 -0700 Subject: [PATCH 08/16] fix: no recent searches for dm search bar --- src/Features/General/NoRecentSearches.x | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/General/NoRecentSearches.x b/src/Features/General/NoRecentSearches.x index 6f921597..ec5e8899 100644 --- a/src/Features/General/NoRecentSearches.x +++ b/src/Features/General/NoRecentSearches.x @@ -38,13 +38,13 @@ // Recent dm message recipients search bar %hook IGDirectRecipientRecentSearchStorage -- (id)initWithDiskManager:(id)arg1 directCache:(id)arg2 userStore:(id)arg3 currentUser:(id)arg4 featureSets:(id)arg5 { +- (id)initWithDiskManager:(id)arg1 directRepo:(id)arg2 userMap:(id)arg3 currentUser:(id)arg4 launcherSet:(id)arg5 { if ([SCIUtils getBoolPref:@"no_recent_searches"]) { NSLog(@"[SCInsta] Disabling recent searches"); return nil; } - return %orig; + return %orig(arg1, arg2, arg3, arg4, arg5); } %end \ No newline at end of file From d7e224b402106e3fe0021e81ea1284c9be8e273d Mon Sep 17 00:00:00 2001 From: SoCuul Date: Thu, 19 Mar 2026 17:07:40 -0700 Subject: [PATCH 09/16] style: add mark comments for sections --- src/Tweak.x | 12 +++++++++--- src/Utils.h | 17 +++++++++-------- src/Utils.m | 54 ++++++++++++++++++++++++++++------------------------- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/Tweak.x b/src/Tweak.x index a99654ca..21a53716 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -18,7 +18,7 @@ NSString *SCIVersionString = @"v1.2.0-dev"; // Variables that work across features BOOL dmVisualMsgsViewedButtonEnabled = false; -// Tweak first-time setup +// MARK: Tweak first-time setup %hook IGInstagramAppDelegate - (_Bool)application:(UIApplication *)application willFinishLaunchingWithOptions:(id)arg2 { // Default SCInsta config @@ -93,6 +93,8 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; } %end +// MARK: Liquid glass + %hook IGDSLauncherConfig - (_Bool)isLiquidGlassInAppNotificationEnabled { return [SCIUtils liquidGlassEnabledBool:%orig]; @@ -111,6 +113,8 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; } %end +// MARK: Bug reports + // Disable sending modded insta bug reports %hook IGWindow - (void)showDebugMenu { @@ -130,6 +134,8 @@ shouldPersistLastBugReportId:(id)arg6 } %end +// MARK: Screenshots + // Disable anti-screenshot feature on visual messages %hook IGStoryViewerContainerView - (void)setShouldBlockScreenshot:(BOOL)arg1 viewModel:(id)arg2 { VOID_HANDLESCREENSHOT(%orig); } @@ -190,7 +196,7 @@ shouldPersistLastBugReportId:(id)arg6 ///////////////////////////////////////////////////////////////////////////// -// Hide items +// MARK: Hide items // Direct suggested chats (in search bar) BOOL showSearchSectionLabelForTag(NSInteger tag) { @@ -600,7 +606,7 @@ BOOL showSearchSectionLabelForTag(NSInteger tag) { ///////////////////////////////////////////////////////////////////////////// -// Confirm buttons +// MARK: Confirm buttons %hook IGFeedItemUFICell - (void)UFIButtonBarDidTapOnLike:(id)arg1 { diff --git a/src/Utils.h b/src/Utils.h index 91dc6612..442c0ff9 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -21,15 +21,22 @@ @interface SCIUtils : NSObject +// Preferences + (BOOL)getBoolPref:(NSString *)key; + (double)getDoublePref:(NSString *)key; + (NSString *)getStringPref:(NSString *)key; +// Misc ++ (NSString *)IGVersionString; ++ (BOOL)isNotch; + ++ (BOOL)existingLongPressGestureRecognizerForView:(UIView *)view; + + (_Bool)liquidGlassEnabledBool:(_Bool)fallback; + (void)cleanCache; -// Displaying View Controllers +// Display View Controllers + (void)showQuickLookVC:(NSArray *)items; + (void)showShareVC:(id)item; + (void)showSettingsVC:(UIWindow *)window; @@ -51,17 +58,11 @@ + (NSURL *)getVideoUrl:(IGVideo *)video; + (NSURL *)getVideoUrlForMedia:(IGMedia *)media; -// View Controllers +// View Controller Helpers + (UIViewController *)viewControllerForView:(UIView *)view; + (UIViewController *)viewControllerForAncestralView:(UIView *)view; + (UIViewController *)nearestViewControllerForView:(UIView *)view; -// Functions -+ (NSString *)IGVersionString; -+ (BOOL)isNotch; - -+ (BOOL)existingLongPressGestureRecognizerForView:(UIView *)view; - // Alerts + (BOOL)showConfirmation:(void(^)(void))okHandler title:(NSString *)title; + (BOOL)showConfirmation:(void(^)(void))okHandler cancelHandler:(void(^)(void))cancelHandler title:(NSString *)title; diff --git a/src/Utils.m b/src/Utils.m index f91c5986..9c167b56 100644 --- a/src/Utils.m +++ b/src/Utils.m @@ -18,6 +18,27 @@ + (NSString *)getStringPref:(NSString *)key { return [[NSUserDefaults standardUserDefaults] stringForKey:key]; } +// MARK: Misc + ++ (NSString *)IGVersionString { + return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; +}; ++ (BOOL)isNotch { + return [[[UIApplication sharedApplication] keyWindow] safeAreaInsets].bottom > 0; +}; + ++ (BOOL)existingLongPressGestureRecognizerForView:(UIView *)view { + NSArray *allRecognizers = view.gestureRecognizers; + + for (UIGestureRecognizer *recognizer in allRecognizers) { + if ([[recognizer class] isSubclassOfClass:[UILongPressGestureRecognizer class]]) { + return YES; + } + } + + return NO; +} + + (_Bool)liquidGlassEnabledBool:(_Bool)fallback { BOOL setting = [SCIUtils getBoolPref:@"liquid_glass_surfaces"]; return setting ? true : fallback; @@ -72,7 +93,7 @@ + (void)cleanCache { } -// Displaying View Controllers +// MARK: Display View Controllers + (void)showQuickLookVC:(NSArray *)items { QLPreviewController *previewController = [[QLPreviewController alloc] init]; QuickLookDelegate *quickLookDelegate = [[QuickLookDelegate alloc] initWithPreviewItemURLs:items]; @@ -97,12 +118,12 @@ + (void)showSettingsVC:(UIWindow *)window { [rootController presentViewController:navigationController animated:YES completion:nil]; } -// Colours +// MARK: Colours + (UIColor *)SCIColor_Primary { return [UIColor colorWithRed:0/255.0 green:152/255.0 blue:254/255.0 alpha:1]; }; -// Errors +// MARK: Errors + (NSError *)errorWithDescription:(NSString *)errorDesc { return [self errorWithDescription:errorDesc code:1]; } @@ -125,7 +146,7 @@ + (JGProgressHUD *)showErrorHUDWithDescription:(NSString *)errorDesc dismissAfte return hud; } -// Media +// MARK: Media + (NSURL *)getPhotoUrl:(IGPhoto *)photo { if (!photo) return nil; @@ -167,7 +188,7 @@ + (NSURL *)getVideoUrlForMedia:(IGMedia *)media { return [SCIUtils getVideoUrl:video]; } -// View Controllers +// MARK: View Controller Helpers + (UIViewController *)viewControllerForView:(UIView *)view { NSString *viewDelegate = @"viewDelegate"; if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) { @@ -191,26 +212,9 @@ + (UIViewController *)nearestViewControllerForView:(UIView *)view { } // Functions -+ (NSString *)IGVersionString { - return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; -}; -+ (BOOL)isNotch { - return [[[UIApplication sharedApplication] keyWindow] safeAreaInsets].bottom > 0; -}; - -+ (BOOL)existingLongPressGestureRecognizerForView:(UIView *)view { - NSArray *allRecognizers = view.gestureRecognizers; - for (UIGestureRecognizer *recognizer in allRecognizers) { - if ([[recognizer class] isSubclassOfClass:[UILongPressGestureRecognizer class]]) { - return YES; - } - } - - return NO; -} -// Alerts +// MARK: Alerts + (BOOL)showConfirmation:(void(^)(void))okHandler title:(NSString *)title { UIAlertController* alert = [UIAlertController alertControllerWithTitle:title message:@"Are you sure?" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { @@ -253,7 +257,7 @@ + (void)showRestartConfirmation { [topMostController() presentViewController:alert animated:YES completion:nil]; }; -// Toasts +// MARK: Toasts + (void)showToastForDuration:(double)duration title:(NSString *)title { [SCIUtils showToastForDuration:duration title:title subtitle:nil]; } @@ -282,7 +286,7 @@ + (void)showToastForDuration:(double)duration title:(NSString *)title subtitle:( [toastPresenter showAlertWithViewModel:model isAnimated:true animationDuration:duration presentationPriority:0 tapActionBlock:nil presentedHandler:nil dismissedHandler:nil]; } -// Math +// MARK: Math + (NSUInteger)decimalPlacesInDouble:(double)value { NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; From b2b1b97dcfcbf3fc381016134e9a751814e75463 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Thu, 19 Mar 2026 17:19:04 -0700 Subject: [PATCH 10/16] feat: hide messages tab --- README.md | 1 + src/Features/General/Navigation.xm | 20 ++++++++++++++++++++ src/InstagramHeaders.h | 3 +++ src/Settings/TweakSettings.m | 1 + 4 files changed, 25 insertions(+) diff --git a/README.md b/README.md index fba0b33f..56642288 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ A feature-rich tweak for Instagram on iOS!\ - Hiding tabs - Hide feed tab - Hide explore tab + - Hide messages tab - Hide reels tab - Hide create tab diff --git a/src/Features/General/Navigation.xm b/src/Features/General/Navigation.xm index ccf9d04c..bccbde88 100644 --- a/src/Features/General/Navigation.xm +++ b/src/Features/General/Navigation.xm @@ -13,6 +13,11 @@ BOOL isSurfaceShown(IGMainAppSurfaceIntent *surface) { isShown = NO; } + // Messages + else if ([[surface tabStringFromSurfaceIntent] isEqualToString:@"DIRECT"] && [SCIUtils getBoolPref:@"hide_messages_tab"]) { + isShown = NO; + } + // Explore else if ([[surface tabStringFromSurfaceIntent] isEqualToString:@"SEARCH"] && [SCIUtils getBoolPref:@"hide_explore_tab"]) { isShown = NO; @@ -97,4 +102,19 @@ NSArray *filterSurfacesArray(NSArray *surfaces) { - (void)setIsTabSwipingEnabled:(BOOL)arg1 { return; } +%end + +%hook IGHomeFeedHeaderView +- (void)didMoveToWindow { + %orig; + + if ([SCIUtils getBoolPref:@"hide_messages_tab"]) { + UIButton *rightButton = [self valueForKey:@"rightButton"]; + if (rightButton) { + NSLog(@"[SCInsta] Hiding messages tab (on feed)"); + + [rightButton removeFromSuperview]; + } + } +} %end \ No newline at end of file diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index 23adcdf8..294c3c8f 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -287,6 +287,9 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gr; // new @end +@interface IGHomeFeedHeaderView : UIView +@end + @interface IGHomeFeedHeaderViewController - (void)headerDidLongPressLogo:(id)arg1; @end diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index e61f2b18..d407ec7b 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -162,6 +162,7 @@ + (NSArray *)sections { @"rows": @[ [SCISetting switchCellWithTitle:@"Hide feed tab" subtitle:@"Hides the feed/home tab on the bottom navigation bar" defaultsKey:@"hide_feed_tab" requiresRestart:YES], [SCISetting switchCellWithTitle:@"Hide explore tab" subtitle:@"Hides the explore/search tab on the bottom navigation bar" defaultsKey:@"hide_explore_tab" requiresRestart:YES], + [SCISetting switchCellWithTitle:@"Hide messages tab" subtitle:@"Hides the direct messages tab on the bottom navigation bar" defaultsKey:@"hide_messages_tab" requiresRestart:YES], [SCISetting switchCellWithTitle:@"Hide reels tab" subtitle:@"Hides the reels tab on the bottom navigation bar" defaultsKey:@"hide_reels_tab" requiresRestart:YES], [SCISetting switchCellWithTitle:@"Hide create tab" subtitle:@"Hides the create tab on the bottom navigation bar" defaultsKey:@"hide_create_tab" requiresRestart:YES] ] From 62a540eb2c968ff9c1be07818895c8f49e411ce6 Mon Sep 17 00:00:00 2001 From: SoCuul Date: Mon, 23 Mar 2026 01:09:55 -0700 Subject: [PATCH 11/16] fix: hide ai rewrite button in story message composer fields --- src/Features/General/HideMetaAI.xm | 45 ++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/Features/General/HideMetaAI.xm b/src/Features/General/HideMetaAI.xm index 8dbdce0c..b7b426a6 100644 --- a/src/Features/General/HideMetaAI.xm +++ b/src/Features/General/HideMetaAI.xm @@ -135,22 +135,35 @@ // Write with meta ai in message composer %hook IGDirectComposer - (id)initWithLayoutSpecProvider:(id)arg1 - userLauncherSetProviding:(id)arg2 + userSession:(id)arg2 + userLauncherSet:(id)arg3 config:(IGDirectComposerConfig *)config - style:(id)arg4 - text:(id)arg5 + style:(id)arg5 + text:(id)arg6 { - return %orig(arg1, arg2, [self patchConfig:config], arg4, arg5); + return %orig(arg1, arg2, arg3, [self patchConfig:config], arg5, arg6); } - (id)initWithLayoutSpecProvider:(id)arg1 - userLauncherSetProviding:(id)arg2 + userSession:(id)arg2 + userLauncherSet:(id)arg3 config:(IGDirectComposerConfig *)config - style:(id)arg4 - text:(id)arg5 - shouldUpdateModeLater:(BOOL)arg6 + style:(id)arg5 + text:(id)arg6 + shouldUpdateModeLater:(BOOL)arg7 { - return %orig(arg1, arg2, [self patchConfig:config], arg4, arg5, arg6); + return %orig(arg1, arg2, arg3, [self patchConfig:config], arg5, arg6, arg7); +} + +- (id)_initializeWithLayoutSpecProvider:(id)arg1 + userSession:(id)arg2 + userLauncherSet:(id)arg3 + config:(IGDirectComposerConfig *)config + style:(id)arg5 + text:(id)arg6 + shouldUpdateModeLater:(BOOL)arg7 +{ + return %orig(arg1, arg2, arg3, [self patchConfig:config], arg5, arg6, arg7); } - (void)setConfig:(IGDirectComposerConfig *)config { @@ -178,6 +191,20 @@ } %end +// Demangled name: IGAIRewrite.IGAIRewriteStoryRepliesPresenter +%hook _TtC11IGAIRewrite32IGAIRewriteStoryRepliesPresenter +- (BOOL)shouldShowAIRewriteButton:(id)arg1 input:(id)arg2 { + if ([SCIUtils getBoolPref:@"hide_meta_ai"]) { + NSLog(@"[SCInsta] Hiding meta ai: disable ai rewrite story reply presenter"); + + return NO; + } + + return %orig(arg1, arg2); +} + +%end + // Direct sticker tray picker view %hook IGStickerTrayListAdapterDataSource - (id)objectsForListAdapter:(id)arg1 { From a9493c1afce0129a0e2da909d6228ea4821c3f8e Mon Sep 17 00:00:00 2001 From: Mikasa-san Date: Tue, 21 Apr 2026 16:54:31 +0400 Subject: [PATCH 12/16] Adds Authentication Option --- src/Settings/SCISecurityViewController.h | 5 +++ src/Settings/SCISecurityViewController.m | 51 ++++++++++++++++++++++++ src/Settings/TweakSettings.m | 6 +++ src/Tweak.x | 23 +++++++++++ 4 files changed, 85 insertions(+) create mode 100644 src/Settings/SCISecurityViewController.h create mode 100644 src/Settings/SCISecurityViewController.m diff --git a/src/Settings/SCISecurityViewController.h b/src/Settings/SCISecurityViewController.h new file mode 100644 index 00000000..02058ff8 --- /dev/null +++ b/src/Settings/SCISecurityViewController.h @@ -0,0 +1,5 @@ +#import +#import + +@interface SCISecurityViewController : UIViewController +@end diff --git a/src/Settings/SCISecurityViewController.m b/src/Settings/SCISecurityViewController.m new file mode 100644 index 00000000..a062b76c --- /dev/null +++ b/src/Settings/SCISecurityViewController.m @@ -0,0 +1,51 @@ +#import "SecurityViewController.h" + +@implementation SCISecurityViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; + UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; + blurView.frame = self.view.bounds; + [self.view addSubview:blurView]; + + UIButton *authenticateButton = [[UIButton alloc] initWithFrame:CGRectMake(20, 20, 200, 60)]; + [authenticateButton setTitle:@"Click to unlock app" forState:UIControlStateNormal]; + authenticateButton.center = self.view.center; + [authenticateButton addTarget:self action:@selector(authenticateButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:authenticateButton]; + + [self authenticate]; +} + +- (void)authenticateButtonTapped:(id)sender { + [self authenticate]; +} + +- (void)authenticate { + LAContext *context = [[LAContext alloc] init]; + NSError *error = nil; + + NSLog(@"[SCInsta] Padlock authentication: Prompting for unlock"); + + if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error]) { + NSString *reason = @"Authenticate to unlock app"; + + [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:reason reply:^(BOOL success, NSError *authenticationError) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (success) { + [self dismissViewControllerAnimated:YES completion:nil]; + + NSLog(@"[SCInsta] Padlock authentication: Unlock success"); + } else { + NSLog(@"[SCInsta] Padlock authentication: Unlock failed"); + } + }); + }]; + } else { + NSLog(@"[SCInsta] Padlock authentication: Device authentication not available"); + } +} + +@end diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index d407ec7b..2bffbdbb 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -41,6 +41,12 @@ + (NSArray *)sections { [SCISetting switchCellWithTitle:@"Disable app haptics" subtitle:@"Disables haptics/vibrations within the Instagram app" defaultsKey:@"disable_haptics"] ] }, + @{ + @"header": @"Security", + @"rows": @[ + [SCISetting switchCellWithTitle:@"Pad Lock" subtitle:@"Requires Face ID, Touch ID, or passcode to unlock Instagram" defaultsKey:@"Padlock"], + ] + }, @{ @"header": @"Notes", @"rows": @[ diff --git a/src/Tweak.x b/src/Tweak.x index 21a53716..f7424eec 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -20,6 +20,9 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; // MARK: Tweak first-time setup %hook IGInstagramAppDelegate + +BOOL isAuthenticationShown = FALSE; + - (_Bool)application:(UIApplication *)application willFinishLaunchingWithOptions:(id)arg2 { // Default SCInsta config NSDictionary *sciDefaults = @{ @@ -90,7 +93,27 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; if ([SCIUtils getBoolPref:@"flex_app_start"]) { [[objc_getClass("FLEXManager") sharedManager] showExplorer]; } + + // Padlock (biometric auth) + if ([SCIUtils getBoolPref:@"Padlock"] && !isAuthenticationShown) { + UIViewController *rootController = [[self window] rootViewController]; + SCISecurityViewController *securityViewController = [SCISecurityViewController new]; + securityViewController.modalPresentationStyle = UIModalPresentationOverFullScreen; + [rootController presentViewController:securityViewController animated:YES completion:nil]; + + isAuthenticationShown = TRUE; + + NSLog(@"[Hypergram] Padlock authentication: App enabled"); + } } + +- (void)applicationWillEnterForeground:(id)arg1 { + %orig; + + // Reset padlock status + isAuthenticationShown = FALSE; +} + %end // MARK: Liquid glass From f17ac42f66d572f2f51896b7199b8a063e793bc4 Mon Sep 17 00:00:00 2001 From: Mikasa-san Date: Tue, 21 Apr 2026 17:07:36 +0400 Subject: [PATCH 13/16] Fix header --- src/Settings/SCISecurityViewController.m | 2 +- src/Utils.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Settings/SCISecurityViewController.m b/src/Settings/SCISecurityViewController.m index a062b76c..fef72c34 100644 --- a/src/Settings/SCISecurityViewController.m +++ b/src/Settings/SCISecurityViewController.m @@ -1,4 +1,4 @@ -#import "SecurityViewController.h" +#import "SCISecurityViewController.h" @implementation SCISecurityViewController diff --git a/src/Utils.h b/src/Utils.h index 442c0ff9..2cdfbd59 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -11,6 +11,8 @@ #import "Settings/SCISettingsViewController.h" +#import "Settings/SCISecurityViewController.h" + #define SCILog(fmt, ...) \ do { \ NSString *tmpStr = [NSString stringWithFormat:(fmt), ##__VA_ARGS__]; \ From 875d06e35506012f6fe4f9e8fe9224ab8b9e3878 Mon Sep 17 00:00:00 2001 From: Mikasa-san Date: Tue, 21 Apr 2026 18:02:55 +0400 Subject: [PATCH 14/16] Update SeenButtons.x - Fix type mismatch in right bar button filtering - Handle nil bar button item arrays safely - Prevent duplicate custom navigation bar buttons - Clean up seen/replay button creation logic - Standardize BOOL usage - Improve replay toggle tint/state handling - Ensure playback hooks call %orig correctly when needed - Improve readability and maintainability --- src/Features/StoriesAndMessages/SeenButtons.x | 137 +++++++++++++----- 1 file changed, 97 insertions(+), 40 deletions(-) diff --git a/src/Features/StoriesAndMessages/SeenButtons.x b/src/Features/StoriesAndMessages/SeenButtons.x index 4a8ab14c..154a05b8 100644 --- a/src/Features/StoriesAndMessages/SeenButtons.x +++ b/src/Features/StoriesAndMessages/SeenButtons.x @@ -5,92 +5,149 @@ // Seen buttons (in DMs) // - Enables no seen for messages // - Enables unlimited views of DM visual messages + %hook IGTallNavigationBarView -- (void)setRightBarButtonItems:(NSArray *)items { - NSMutableArray *new_items = [[items filteredArrayUsingPredicate: - [NSPredicate predicateWithBlock:^BOOL(UIView *value, NSDictionary *_) { + +- (void)setRightBarButtonItems:(NSArray *)items { + NSArray *originalItems = items ?: @[]; + + NSMutableArray *newItems = [[originalItems filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(UIBarButtonItem *value, NSDictionary *bindings) { + if (![value isKindOfClass:[UIBarButtonItem class]]) { + return YES; + } + if ([SCIUtils getBoolPref:@"hide_reels_blend"]) { return ![value.accessibilityIdentifier isEqualToString:@"blend-button"]; } - return true; + return YES; }] ] mutableCopy]; // Messages seen + if (!newItems) { + newItems = [NSMutableArray array]; + } + + // Messages seen button if ([SCIUtils getBoolPref:@"remove_lastseen"]) { - UIBarButtonItem *seenButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"checkmark.message"] style:UIBarButtonItemStylePlain target:self action:@selector(seenButtonHandler:)]; - [new_items addObject:seenButton]; + BOOL alreadyHasSeenButton = NO; + + for (UIBarButtonItem *item in newItems) { + if (item.action == @selector(seenButtonHandler:)) { + alreadyHasSeenButton = YES; + break; + } + } + + if (!alreadyHasSeenButton) { + UIBarButtonItem *seenButton = [[UIBarButtonItem alloc] + initWithImage:[UIImage systemImageNamed:@"checkmark.message"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(seenButtonHandler:)]; + [newItems addObject:seenButton]; + } } - // DM visual messages viewed + // DM visual messages viewed button if ([SCIUtils getBoolPref:@"unlimited_replay"]) { - UIBarButtonItem *dmVisualMsgsViewedButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"photo.badge.checkmark"] style:UIBarButtonItemStylePlain target:self action:@selector(dmVisualMsgsViewedButtonHandler:)]; - [new_items addObject:dmVisualMsgsViewedButton]; + BOOL alreadyHasReplayButton = NO; + + for (UIBarButtonItem *item in newItems) { + if (item.action == @selector(dmVisualMsgsViewedButtonHandler:)) { + alreadyHasReplayButton = YES; + break; + } + } + + if (!alreadyHasReplayButton) { + UIBarButtonItem *dmVisualMsgsViewedButton = [[UIBarButtonItem alloc] + initWithImage:[UIImage systemImageNamed:@"photo.badge.checkmark"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(dmVisualMsgsViewedButtonHandler:)]; - if (dmVisualMsgsViewedButtonEnabled) { - [dmVisualMsgsViewedButton setTintColor:SCIUtils.SCIColor_Primary]; - } else { - [dmVisualMsgsViewedButton setTintColor:UIColor.labelColor]; + dmVisualMsgsViewedButton.tintColor = dmVisualMsgsViewedButtonEnabled + ? SCIUtils.SCIColor_Primary + : UIColor.labelColor; + + [newItems addObject:dmVisualMsgsViewedButton]; } } - %orig([new_items copy]); + %orig([newItems copy]); } - // Messages seen button -%new - (void)seenButtonHandler:(UIBarButtonItem *)sender { +%new +- (void)seenButtonHandler:(UIBarButtonItem *)sender { UIViewController *nearestVC = [SCIUtils nearestViewControllerForView:self]; if ([nearestVC isKindOfClass:%c(IGDirectThreadViewController)]) { [(IGDirectThreadViewController *)nearestVC markLastMessageAsSeen]; - [SCIUtils showToastForDuration:2.5 title:@"Marked messages as seen"]; } } // DM visual messages viewed button -%new - (void)dmVisualMsgsViewedButtonHandler:(UIBarButtonItem *)sender { - if (dmVisualMsgsViewedButtonEnabled) { - dmVisualMsgsViewedButtonEnabled = false; - [sender setTintColor:UIColor.labelColor]; +%new +- (void)dmVisualMsgsViewedButtonHandler:(UIBarButtonItem *)sender { + dmVisualMsgsViewedButtonEnabled = !dmVisualMsgsViewedButtonEnabled; - [SCIUtils showToastForDuration:4.5 title:@"Visual messages can be replayed without expiring"]; - } - else { - dmVisualMsgsViewedButtonEnabled = true; - [sender setTintColor:SCIUtils.SCIColor_Primary]; + sender.tintColor = dmVisualMsgsViewedButtonEnabled + ? SCIUtils.SCIColor_Primary + : UIColor.labelColor; + if (dmVisualMsgsViewedButtonEnabled) { [SCIUtils showToastForDuration:4.5 title:@"Visual messages will now expire after viewing"]; + } else { + [SCIUtils showToastForDuration:4.5 title:@"Visual messages can be replayed without expiring"]; } } + %end // Messages seen logic %hook IGDirectThreadViewListAdapterDataSource + - (BOOL)shouldUpdateLastSeenMessage { if ([SCIUtils getBoolPref:@"remove_lastseen"]) { - return false; + return NO; } - + return %orig; } + %end // DM stories viewed logic %hook IGDirectVisualMessageViewerEventHandler -- (void)visualMessageViewerController:(id)arg1 didBeginPlaybackForVisualMessage:(id)arg2 atIndex:(NSInteger)arg3 { - if ([SCIUtils getBoolPref:@"unlimited_replay"]) { - // Check if dm stories should be marked as viewed - if (dmVisualMsgsViewedButtonEnabled) { - %orig; - } + +- (void)visualMessageViewerController:(id)arg1 +didBeginPlaybackForVisualMessage:(id)arg2 + atIndex:(NSInteger)arg3 { + if (![SCIUtils getBoolPref:@"unlimited_replay"]) { + %orig; + return; + } + + if (dmVisualMsgsViewedButtonEnabled) { + %orig; } } -- (void)visualMessageViewerController:(id)arg1 didEndPlaybackForVisualMessage:(id)arg2 atIndex:(NSInteger)arg3 mediaCurrentTime:(CGFloat)arg4 forNavType:(NSInteger)arg5 { - if ([SCIUtils getBoolPref:@"unlimited_replay"]) { - // Check if dm stories should be marked as viewed - if (dmVisualMsgsViewedButtonEnabled) { - %orig; - } + +- (void)visualMessageViewerController:(id)arg1 +didEndPlaybackForVisualMessage:(id)arg2 + atIndex:(NSInteger)arg3 + mediaCurrentTime:(CGFloat)arg4 + forNavType:(NSInteger)arg5 { + if (![SCIUtils getBoolPref:@"unlimited_replay"]) { + %orig; + return; + } + + if (dmVisualMsgsViewedButtonEnabled) { + %orig; } } + %end \ No newline at end of file From 32184d26d161e8e691b705d62bd0390c2359685b Mon Sep 17 00:00:00 2001 From: Mikasa-san Date: Tue, 21 Apr 2026 19:28:43 +0400 Subject: [PATCH 15/16] typo --- src/Tweak.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tweak.x b/src/Tweak.x index f7424eec..10459aeb 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -103,7 +103,7 @@ BOOL isAuthenticationShown = FALSE; isAuthenticationShown = TRUE; - NSLog(@"[Hypergram] Padlock authentication: App enabled"); + NSLog(@"[SCInsta] Padlock authentication: App enabled"); } } From 54b4c37dbb2ed3b841591f67c6efd1107ce2b26d Mon Sep 17 00:00:00 2001 From: Mikasa-san Date: Sun, 26 Apr 2026 06:45:51 +0400 Subject: [PATCH 16/16] Fix Reels & Story video download & improve addLongPressGestureRecognizer --- src/Features/Media/MediaDownload.xm | 237 +++++++++++++--------------- src/InstagramHeaders.h | 2 + 2 files changed, 108 insertions(+), 131 deletions(-) diff --git a/src/Features/Media/MediaDownload.xm b/src/Features/Media/MediaDownload.xm index 88a92158..b40aa9ec 100644 --- a/src/Features/Media/MediaDownload.xm +++ b/src/Features/Media/MediaDownload.xm @@ -4,6 +4,7 @@ static SCIDownloadDelegate *imageDownloadDelegate; static SCIDownloadDelegate *videoDownloadDelegate; +static char SCILongPressGestureKey; static void initDownloaders () { // Init downloaders only once @@ -13,6 +14,17 @@ static void initDownloaders () { videoDownloadDelegate = [[SCIDownloadDelegate alloc] initWithAction:share showProgress:YES]; }); } +static void SCIAddLongPressGestureRecognizer(UIView *view) { + + if (!view || objc_getAssociatedObject(view, &SCILongPressGestureKey)) return; + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:view action:@selector(handleLongPress:)]; + longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; + longPress.numberOfTouchesRequired = (NSUInteger)[SCIUtils getDoublePref:@"dw_finger_count"]; + longPress.cancelsTouchesInView = NO; + [view addGestureRecognizer:longPress]; + objc_setAssociatedObject(view, &SCILongPressGestureKey, longPress, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + +} /* * Feed * */ @@ -27,15 +39,8 @@ static void initDownloaders () { return; } -%new - (void)addLongPressGestureRecognizer { - NSLog(@"[SCInsta] Adding feed photo download long press gesture recognizer"); - - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; - - [self addGestureRecognizer:longPress]; -} +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} %new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { if (sender.state != UIGestureRecognizerStateBegan) return; @@ -80,15 +85,8 @@ static void initDownloaders () { return; } -%new - (void)addLongPressGestureRecognizer { - NSLog(@"[SCInsta] Adding feed video download long press gesture recognizer"); - - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; - - [self addGestureRecognizer:longPress]; -} +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} %new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { if (sender.state != UIGestureRecognizerStateBegan) return; @@ -121,15 +119,8 @@ static void initDownloaders () { return; } -%new - (void)addLongPressGestureRecognizer { - NSLog(@"[SCInsta] Adding reels photo download long press gesture recognizer"); - - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; - - [self addGestureRecognizer:longPress]; -} +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} %new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { if (sender.state != UIGestureRecognizerStateBegan) return; @@ -153,39 +144,56 @@ static void initDownloaders () { // Download reels (videos) %hook IGSundialViewerVideoCell - (void)didMoveToSuperview { - %orig; - - if ([SCIUtils getBoolPref:@"dw_reels"]) { - [self addLongPressGestureRecognizer]; - } + %orig; - return; + if ([SCIUtils getBoolPref:@"dw_reels"]) { + [self addLongPressGestureRecognizer]; + } } -%new - (void)addLongPressGestureRecognizer { - NSLog(@"[SCInsta] Adding reels video download long press gesture recognizer"); - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} - [self addGestureRecognizer:longPress]; -} -%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { - if (sender.state != UIGestureRecognizerStateBegan) return; - - NSURL *videoUrl = [SCIUtils getVideoUrlForMedia:self.video]; - if (!videoUrl) { - [SCIUtils showErrorHUDWithDescription:@"Could not extract video url from reel"]; +%new +- (void)handleLongPress:(UILongPressGestureRecognizer *)sender { + if (sender.state != UIGestureRecognizerStateBegan) return; - return; - } + id media = nil; - // Download video & show in share menu - initDownloaders(); - [videoDownloadDelegate downloadFileWithURL:videoUrl - fileExtension:[[videoUrl lastPathComponent] pathExtension] - hudLabel:nil]; + @try { + media = [self valueForKey:@"_mediaPassthrough"]; + } @catch (NSException *exception) { + media = nil; + } + + if (!media) { + @try { + id mediaInfo = [self valueForKey:@"_mediaInfo"]; + if ([mediaInfo respondsToSelector:@selector(media)]) { + media = [mediaInfo performSelector:@selector(media)]; + } + } @catch (NSException *exception) { + media = nil; + } + } + + if (!media) { + [SCIUtils showErrorHUDWithDescription:@"Could not find reel media object"]; + return; + } + + NSURL *videoUrl = [SCIUtils getVideoUrlForMedia:media]; + + if (!videoUrl) { + [SCIUtils showErrorHUDWithDescription:@"Could not extract video url from reel"]; + return; + } + + initDownloaders(); + + [videoDownloadDelegate downloadFileWithURL:videoUrl fileExtension:[[videoUrl lastPathComponent] pathExtension] hudLabel:nil]; } + %end @@ -202,15 +210,8 @@ static void initDownloaders () { return; } -%new - (void)addLongPressGestureRecognizer { - NSLog(@"[SCInsta] Adding story photo download long press gesture recognizer"); - - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; - - [self addGestureRecognizer:longPress]; -} +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} %new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { if (sender.state != UIGestureRecognizerStateBegan) return; @@ -223,9 +224,7 @@ static void initDownloaders () { // Download image & show in share menu initDownloaders(); - [imageDownloadDelegate downloadFileWithURL:photoUrl - fileExtension:[[photoUrl lastPathComponent]pathExtension] - hudLabel:nil]; + [imageDownloadDelegate downloadFileWithURL:photoUrl fileExtension:[[photoUrl lastPathComponent]pathExtension] hudLabel:nil]; } %end @@ -240,15 +239,8 @@ static void initDownloaders () { return; } -%new - (void)addLongPressGestureRecognizer { - //NSLog(@"[SCInsta] Adding story video download long press gesture recognizer"); - - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; - - [self addGestureRecognizer:longPress]; -} +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} %new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { if (sender.state != UIGestureRecognizerStateBegan) return; @@ -262,70 +254,55 @@ static void initDownloaders () { // Download video & show in share menu initDownloaders(); - [videoDownloadDelegate downloadFileWithURL:videoUrl - fileExtension:[[videoUrl lastPathComponent] pathExtension] - hudLabel:nil]; + [videoDownloadDelegate downloadFileWithURL:videoUrl fileExtension:[[videoUrl lastPathComponent] pathExtension] hudLabel:nil]; } %end // Download story (videos, legacy) %hook IGStoryVideoView -- (void)didMoveToSuperview { - %orig; - if ([SCIUtils getBoolPref:@"dw_story"]) { - [self addLongPressGestureRecognizer]; - } +- (void)didMoveToSuperview { + %orig; - return; + if ([SCIUtils getBoolPref:@"dw_story"]) { + [self addLongPressGestureRecognizer]; + } } -%new - (void)addLongPressGestureRecognizer { - //NSLog(@"[SCInsta] Adding story video download long press gesture recognizer"); - - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - longPress.minimumPressDuration = [SCIUtils getDoublePref:@"dw_finger_duration"]; - longPress.numberOfTouchesRequired = [SCIUtils getDoublePref:@"dw_finger_count"]; - - [self addGestureRecognizer:longPress]; +%new +- (void)addLongPressGestureRecognizer {SCIAddLongPressGestureRecognizer((UIView *)self);} + +%new +- (void)handleLongPress:(UILongPressGestureRecognizer *)sender { + if (sender.state != UIGestureRecognizerStateBegan) return; + NSURL *videoUrl = nil; + id item = nil; + if ([self respondsToSelector:@selector(item)]) { + item = [self item]; + } + if (item) { + videoUrl = [SCIUtils getVideoUrlForMedia:item]; + } + if (!videoUrl) { + id provider = nil; + + if ([self respondsToSelector:@selector(videoURLProvider)]) { + provider = [self videoURLProvider]; + } + + if (provider) { + videoUrl = [SCIUtils getVideoUrlForMedia:provider]; + } + } + if (!videoUrl) { + [SCIUtils showErrorHUDWithDescription:@"Could not extract video url from story"]; + return; + } + + initDownloaders(); + + [videoDownloadDelegate downloadFileWithURL:videoUrl fileExtension:[[videoUrl lastPathComponent] pathExtension] hudLabel:nil]; } -%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender { - if (sender.state != UIGestureRecognizerStateBegan) return; - - NSURL *videoUrl; - IGStoryFullscreenSectionController *captionDelegate = self.captionDelegate; - if (captionDelegate) { - videoUrl = [SCIUtils getVideoUrlForMedia:captionDelegate.currentStoryItem]; - } - else { - // Direct messages video player - id parentVC = [SCIUtils nearestViewControllerForView:self]; - if (!parentVC || ![parentVC isKindOfClass:%c(IGDirectVisualMessageViewerController)]) return; - - IGDirectVisualMessageViewerViewModeAwareDataSource *_dataSource = MSHookIvar(parentVC, "_dataSource"); - if (!_dataSource) return; - - IGDirectVisualMessage *_currentMessage = MSHookIvar(_dataSource, "_currentMessage"); - if (!_currentMessage) return; - - IGVideo *rawVideo = _currentMessage.rawVideo; - if (!rawVideo) return; - - videoUrl = [SCIUtils getVideoUrl:rawVideo]; - } - - if (!videoUrl) { - [SCIUtils showErrorHUDWithDescription:@"Could not extract video url from story"]; - - return; - } - - // Download video & show in share menu - initDownloaders(); - [videoDownloadDelegate downloadFileWithURL:videoUrl - fileExtension:[[videoUrl lastPathComponent] pathExtension] - hudLabel:nil]; -} %end @@ -361,8 +338,6 @@ static void initDownloaders () { // Download image & preview in quick look initDownloaders(); - [imageDownloadDelegate downloadFileWithURL:imageUrl - fileExtension:[[imageUrl lastPathComponent] pathExtension] - hudLabel:@"Loading"]; + [imageDownloadDelegate downloadFileWithURL:imageUrl fileExtension:[[imageUrl lastPathComponent] pathExtension] hudLabel:@"Loading"]; } %end \ No newline at end of file diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index 294c3c8f..0ae164bd 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -178,6 +178,8 @@ @end @interface IGStoryVideoView : UIView +@property (nonatomic, readonly) IGMedia *item; +@property (readonly, nonatomic) IGMedia *videoURLProvider; @property (nonatomic, weak, readwrite) IGStoryFullscreenSectionController *captionDelegate; - (void)addLongPressGestureRecognizer; // new