modded scinsta with additional features and fixes for recent instagram version

This commit is contained in:
faroukbmiled
2026-03-29 00:01:05 +01:00
commit 3d133ac333
105 changed files with 11916 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
#import "../../Utils.h"
%hook IGDirectThreadCallButtonsCoordinator
// Voice Call
- (void)_didTapAudioButton:(id)arg1 {
if ([SCIUtils getBoolPref:@"call_confirm"]) {
NSLog(@"[SCInsta] Call confirm triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
// Video Call
- (void)_didTapVideoButton:(id)arg1 {
if ([SCIUtils getBoolPref:@"call_confirm"]) {
NSLog(@"[SCInsta] Call confirm triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
+35
View File
@@ -0,0 +1,35 @@
#import "../../InstagramHeaders.h"
#import "../../Utils.h"
%hook IGDirectThreadThemePickerViewController
- (void)themeNewPickerSectionController:(id)arg1 didSelectTheme:(id)arg2 atIndex:(NSInteger)arg3 {
if ([SCIUtils getBoolPref:@"change_direct_theme_confirm"]) {
NSLog(@"[SCInsta] Confirm change direct theme triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
- (void)themePickerSectionController:(id)arg1 didSelectThemeId:(id)arg2 {
if ([SCIUtils getBoolPref:@"change_direct_theme_confirm"]) {
NSLog(@"[SCInsta] Confirm change direct theme triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
%hook IGDirectThreadThemeKitSwift.IGDirectThreadThemePreviewController
- (void)primaryButtonTapped {
if ([SCIUtils getBoolPref:@"change_direct_theme_confirm"]) {
NSLog(@"[SCInsta] Confirm change direct theme triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
+38
View File
@@ -0,0 +1,38 @@
#import "../../Utils.h"
// Legacy hook (for non ai voices interface)
%hook IGDirectThreadViewController
- (void)voiceRecordViewController:(id)arg1 didRecordAudioClipWithURL:(id)arg2 waveform:(id)arg3 duration:(CGFloat)arg4 entryPoint:(NSInteger)arg5 {
if ([SCIUtils getBoolPref:@"voice_message_confirm"]) {
NSLog(@"[SCInsta] DM audio message confirm triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
// Workaround until I can figure out how to stop long press recording from automatically sending
%hook IGDirectComposer
- (void)_didLongPressVoiceMessage:(id)arg1 {
if ([SCIUtils getBoolPref:@"voice_message_confirm"]) {
return;
} else {
return %orig;
}
}
%end
// Demangled name: IGDirectAIVoiceUIKit.CompactBarContentView
%hook _TtC20IGDirectAIVoiceUIKitP33_5754F7617E0D924F9A84EFA352BBD29A21CompactBarContentView
- (void)didTapSend {
if ([SCIUtils getBoolPref:@"voice_message_confirm"]) {
NSLog(@"[SCInsta] DM audio message confirm triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
+103
View File
@@ -0,0 +1,103 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
////////////////////////////////////////////////////////
#define CONFIRMFOLLOW(orig) \
if ([SCIUtils getBoolPref:@"follow_confirm"]) { \
NSLog(@"[SCInsta] Confirm follow triggered"); \
\
[SCIUtils showConfirmation:^(void) { orig; }]; \
} \
else { \
return orig; \
} \
////////////////////////////////////////////////////////
// Follow button on profile page
%hook IGFollowController
- (void)_didPressFollowButton {
// Get user follow status (check if already following user)
NSInteger UserFollowStatus = self.user.followStatus;
// Only show confirm dialog if user is not following
if (UserFollowStatus == 2) {
CONFIRMFOLLOW(%orig);
}
else {
return %orig;
}
}
%end
// Follow button on discover people page
%hook IGDiscoverPeopleButtonGroupView
- (void)_onFollowButtonTapped:(id)arg1 {
CONFIRMFOLLOW(%orig);
}
- (void)_onFollowingButtonTapped:(id)arg1 {
CONFIRMFOLLOW(%orig);
}
%end
// Suggested for you (home feed & profile) follow button
%hook IGHScrollAYMFCell
- (void)_didTapAYMFActionButton {
CONFIRMFOLLOW(%orig);
}
%end
%hook IGHScrollAYMFActionButton
- (void)_didTapTextActionButton {
CONFIRMFOLLOW(%orig);
}
%end
// Follow button on reels
%hook IGUnifiedVideoFollowButton
- (void)_hackilyHandleOurOwnButtonTaps:(id)arg1 event:(id)arg2 {
CONFIRMFOLLOW(%orig);
}
%end
// Follow text on profile (when collapsed into top bar)
%hook IGProfileViewController
- (void)navigationItemsControllerDidTapHeaderFollowButton:(id)arg1 {
CONFIRMFOLLOW(%orig);
}
%end
// Follow button on suggested friends (in story section)
%hook IGStorySectionController
- (void)followButtonTapped:(id)arg1 cell:(id)arg2 {
CONFIRMFOLLOW(%orig);
}
%end
// Follow all button in group chats (3+ members) people view
static void (*orig_listSectionController)(id, SEL, id, id);
static void hooked_listSectionController(id self, SEL _cmd, id arg1, id arg2) {
if ([SCIUtils getBoolPref:@"follow_confirm"]) {
[SCIUtils showConfirmation:^{
orig_listSectionController(self, _cmd, arg1, arg2);
}];
return;
}
orig_listSectionController(self, _cmd, arg1, arg2);
}
%ctor {
Class cls = objc_getClass("IGDirectDetailMembersKit.IGDirectThreadDetailsMembersListViewController");
if (!cls) return;
MSHookMessageEx(
cls,
@selector(listSectionController:didTapHeaderButtonWithViewModel:),
(IMP)hooked_listSectionController,
(IMP *)&orig_listSectionController
);
}
@@ -0,0 +1,22 @@
#import "../../Utils.h"
%hook IGPendingRequestView
- (void)_onApproveButtonTapped {
if ([SCIUtils getBoolPref:@"follow_request_confirm"]) {
NSLog(@"[SCInsta] Confirm follow request triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
- (void)_onIgnoreButtonTapped {
if ([SCIUtils getBoolPref:@"follow_request_confirm"]) {
NSLog(@"[SCInsta] Confirm follow request triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
+150
View File
@@ -0,0 +1,150 @@
#import "../../Utils.h"
///////////////////////////////////////////////////////////
// Confirmation handlers
#define CONFIRMPOSTLIKE(orig) \
if ([SCIUtils getBoolPref:@"like_confirm"]) { \
NSLog(@"[SCInsta] Confirm post like triggered"); \
\
[SCIUtils showConfirmation:^(void) { orig; }]; \
} \
else { \
return orig; \
} \
#define CONFIRMREELSLIKE(orig) \
if ([SCIUtils getBoolPref:@"like_confirm_reels"]) { \
NSLog(@"[SCInsta] Confirm reels like triggered"); \
\
[SCIUtils showConfirmation:^(void) { orig; }]; \
} \
else { \
return orig; \
} \
///////////////////////////////////////////////////////////
// Liking posts
%hook IGUFIButtonBarView
- (void)_onLikeButtonPressed:(id)arg1 {
CONFIRMPOSTLIKE(%orig);
}
%end
%hook IGFeedPhotoView
- (void)_onDoubleTap:(id)arg1 {
CONFIRMPOSTLIKE(%orig);
}
%end
%hook IGVideoPlayerOverlayContainerView
- (void)_handleDoubleTapGesture:(id)arg1 {
CONFIRMPOSTLIKE(%orig);
}
%end
// Liking reels
%hook IGSundialViewerVideoCell
- (void)controlsOverlayControllerDidTapLikeButton:(id)arg1 {
CONFIRMREELSLIKE(%orig);
}
- (void)controlsOverlayControllerDidLongPressLikeButton:(id)arg1 gestureRecognizer:(id)arg2 {
CONFIRMREELSLIKE(%orig);
}
- (void)gestureController:(id)arg1 didObserveDoubleTap:(id)arg2 {
CONFIRMREELSLIKE(%orig);
}
%end
%hook IGSundialViewerPhotoCell
- (void)controlsOverlayControllerDidTapLikeButton:(id)arg1 {
CONFIRMREELSLIKE(%orig);
}
- (void)gestureController:(id)arg1 didObserveDoubleTap:(id)arg2 {
CONFIRMREELSLIKE(%orig);
}
%end
%hook IGSundialViewerCarouselCell
- (void)controlsOverlayControllerDidTapLikeButton:(id)arg1 {
CONFIRMREELSLIKE(%orig);
}
- (void)gestureController:(id)arg1 didObserveDoubleTap:(id)arg2 {
CONFIRMREELSLIKE(%orig);
}
%end
// Liking comments
%hook IGCommentCellController
- (void)commentCell:(id)arg1 didTapLikeButton:(id)arg2 {
CONFIRMPOSTLIKE(%orig);
}
- (void)commentCell:(id)arg1 didTapLikedByButtonForUser:(id)arg2 {
CONFIRMPOSTLIKE(%orig);
}
- (void)commentCellDidLongPressOnLikeButton:(id)arg1 {
CONFIRMPOSTLIKE(%orig);
}
- (void)commentCellDidEndLongPressOnLikeButton:(id)arg1 {
CONFIRMPOSTLIKE(%orig);
}
- (void)commentCellDidDoubleTap:(id)arg1 {
CONFIRMPOSTLIKE(%orig);
}
%end
%hook IGFeedItemPreviewCommentCell
- (void)_didTapLikeButton {
CONFIRMPOSTLIKE(%orig);
}
%end
// Liking stories
%hook IGStoryFullscreenDefaultFooterView
- (void)_handleLikeTapped {
CONFIRMPOSTLIKE(%orig);
}
- (void)_likeTapped {
CONFIRMPOSTLIKE(%orig);
}
- (void)inputView:(id)arg1 didTapLikeButton:(id)arg2 {
CONFIRMPOSTLIKE(%orig);
}
// For some stupid reason they removed the "liketapped" methods on newer Instagram versions
// Now we have to do a shitty workaround instead :(
// Works 99% of the time, but sometimes clicks get through directly to the like button (somehow)
- (void)layoutSubviews {
%orig;
if (![SCIUtils getBoolPref:@"like_confirm"]) return;
UIButton *likeButton = [self valueForKey:@"likeButton"];
if (!likeButton) return;
// 129115 = L(12) I(9) K(11) E(5)
static NSInteger kOverlayTag = 129115;
if ([likeButton viewWithTag:kOverlayTag]) return;
UIButton *overlay = [UIButton buttonWithType:UIButtonTypeCustom];
overlay.tag = kOverlayTag;
overlay.frame = likeButton.bounds;
overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[overlay addTarget:self action:@selector(overlayTapped:) forControlEvents:UIControlEventTouchUpInside];
[likeButton addSubview:overlay];
}
%new - (void)overlayTapped:(UIButton *)overlay {
UIButton *likeButton = (UIButton *)overlay.superview;
[SCIUtils showConfirmation:^{
dispatch_async(dispatch_get_main_queue(), ^{
[likeButton sendActionsForControlEvents:UIControlEventTouchUpInside];
});
}];
}
%end
// DM like button (seems to be hidden)
%hook IGDirectThreadViewController
- (void)_didTapLikeButton {
CONFIRMPOSTLIKE(%orig);
}
%end
+13
View File
@@ -0,0 +1,13 @@
#import "../../Utils.h"
%hook IGCommentComposer.IGCommentComposerController
- (void)onSendButtonTap {
if ([SCIUtils getBoolPref:@"post_comment_confirm"]) {
NSLog(@"[SCInsta] Confirm post comment triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
+33
View File
@@ -0,0 +1,33 @@
#import "../../Utils.h"
%hook IGDirectThreadViewController
- (void)swipeableScrollManagerDidEndDraggingAboveSwipeThreshold:(id)arg1 {
if ([SCIUtils getBoolPref:@"shh_mode_confirm"]) {
NSLog(@"[SCInsta] Confirm shh mode triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
- (void)shhModeTransitionButtonDidTap:(id)arg1 {
if ([SCIUtils getBoolPref:@"shh_mode_confirm"]) {
NSLog(@"[SCInsta] Confirm shh mode triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
- (void)messageListViewControllerDidToggleShhMode:(id)arg1 {
if ([SCIUtils getBoolPref:@"shh_mode_confirm"]) {
NSLog(@"[SCInsta] Confirm shh mode triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
@@ -0,0 +1,13 @@
#import "../../Utils.h"
%hook IGStoryViewerTapTarget
- (void)_didTap:(id)arg1 forEvent:(id)arg2 {
if ([SCIUtils getBoolPref:@"sticker_interact_confirm"]) {
NSLog(@"[SCInsta] Confirm sticker interact triggered");
[SCIUtils showConfirmation:^(void) { %orig; }];
} else {
return %orig;
}
}
%end
@@ -0,0 +1,35 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
%hook IGStoryTextEntryControlsOverlayView
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"enable_hidden_texteffectsstyles"]) {
// Clear previous option values
[self.animationTypes removeAllObjects];
[self.effectTypes removeAllObjects];
// Generate new animation values
// * Animation effects <= 9 are invalid
// * Animations past the maximum count (changing each app update) will crash the app when selected
for (int i = 10; i <= 76; i++) {
[self.animationTypes addObject:@(i)];
}
// Generate new effect values
// * Effects past the maximum count (changing each app update) will gracefully just do nothing
for (int i = 0; i <= 84; i++) {
[self.effectTypes addObject:@(i)];
}
// Refresh option picker
if ([self respondsToSelector:@selector(reloadData)]) {
NSLog(@"[SCInsta] Enable all text effects: Reloading data...");
[self reloadData];
}
}
}
%end
@@ -0,0 +1,26 @@
%hook IGSundialFeedViewController
- (_Bool)_isHomecomingEnabled {
return true;
}
- (_Bool)_isHomeComingHomeFeed {
return true;
}
%end
%hook IGSundialViewerManagedRequestItem
- (id)initWithMedia:(id)media launcherSet:(id)set isHomecomingEnabled:(_Bool)enabled {
return %orig(media, set, true);
}
%end
%hook IGTabBarViewControllerManager
- (_Bool)_isHomecomingEnabled {
return true;
}
%end
%hook IGMainAppSurfaceIntent
+ (id)resolvedHomeAppSurfaceIntentWithIsHomecomingEnabled:(_Bool)enabled {
return %orig(true);
}
%end
+10
View File
@@ -0,0 +1,10 @@
#import "../../Utils.h"
// Demangled name: IGFeedPlayback.IGFeedPlaybackStrategy
%hook _TtC14IGFeedPlayback22IGFeedPlaybackStrategy
- (id)initWithShouldDisableAutoplay:(_Bool)autoplay {
if ([SCIUtils getBoolPref:@"disable_feed_autoplay"]) return %orig(true);
return %orig(autoplay);
}
%end
+334
View File
@@ -0,0 +1,334 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
static NSArray *removeItemsInList(NSArray *list, BOOL isFeed) {
NSArray *originalObjs = list;
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
// Remove suggested posts
if (isFeed && [SCIUtils getBoolPref:@"no_suggested_post"]) {
// Posts
if (
([obj isKindOfClass:%c(IGMedia)] && [((IGMedia *)obj).explorePostInFeed isEqual:@YES])
|| ([obj isKindOfClass:%c(IGFeedGroupHeaderViewModel)] && [[obj title] isEqualToString:@"Suggested Posts"])
) {
NSLog(@"[SCInsta] Removing suggested posts");
continue;
}
// Suggested stories (carousel)
if ([obj isKindOfClass:%c(IGInFeedStoriesTrayModel)]) {
NSLog(@"[SCInsta] Hiding suggested stories carousel");
continue;
}
}
// Remove suggested reels (carousel)
if (isFeed && [SCIUtils getBoolPref:@"no_suggested_reels"]) {
if ([obj isKindOfClass:%c(IGFeedScrollableClipsModel)]) {
NSLog(@"[SCInsta] Hiding suggested reels carousel");
continue;
}
}
// Remove suggested for you (accounts)
if ([SCIUtils getBoolPref:@"no_suggested_account"]) {
// Feed
if (isFeed && [obj isKindOfClass:%c(IGHScrollAYMFModel)]) {
NSLog(@"[SCInsta] Hiding accounts suggested for you (feed)");
continue;
}
// Reels
if ([obj isKindOfClass:%c(IGSuggestedUserInReelsModel)]) {
NSLog(@"[SCInsta] Hiding accounts suggested for you (reels)");
continue;
}
}
// Remove suggested threads posts
if ([SCIUtils getBoolPref:@"no_suggested_threads"]) {
// Feed (carousel)
if (isFeed) {
if ([obj isKindOfClass:%c(IGBloksFeedUnitModel)] || [obj isKindOfClass:objc_getClass("IGThreadsInFeedModels.IGThreadsInFeedModel")]) {
NSLog(@"[SCInsta] Hiding suggested threads posts (carousel)");
continue;
}
}
// Reels
if ([obj isKindOfClass:%c(IGSundialNetegoItem)]) {
NSLog(@"[SCInsta] Hiding suggested threads posts (reels)");
continue;
}
}
// Remove story tray
if (isFeed && [SCIUtils getBoolPref:@"hide_stories_tray"]) {
if ([obj isKindOfClass:%c(IGStoryDataController)]) {
NSLog(@"[SCInsta] Hiding stories tray");
continue;
}
}
// Hide entire feed
if (isFeed && [SCIUtils getBoolPref:@"hide_entire_feed"]) {
if ([obj isKindOfClass:%c(IGPostCreationManager)] || [obj isKindOfClass:%c(IGMedia)] || [obj isKindOfClass:%c(IGEndOfFeedDemarcatorModel)] || [obj isKindOfClass:%c(IGSpinnerLabelViewModel)]) {
NSLog(@"[SCInsta] Hiding entire feed");
continue;
}
}
// Remove ads
if ([SCIUtils getBoolPref:@"hide_ads"]) {
if (
([obj isKindOfClass:%c(IGFeedItem)] && ([obj isSponsored] || [obj isSponsoredApp]))
|| ([obj isKindOfClass:%c(IGDiscoveryGridItem)] && [[obj model] isKindOfClass:%c(IGAdItem)])
|| [obj isKindOfClass:%c(IGAdItem)]
) {
NSLog(@"[SCInsta] Removing ads");
continue;
}
}
[filteredObjs addObject:obj];
}
return [filteredObjs copy];
}
// Suggested posts/reels
%hook IGMainFeedListAdapterDataSource
- (NSArray *)objectsForListAdapter:(id)arg1 {
NSArray *filteredObjs = removeItemsInList(%orig, YES);
// Remove loading spinner at end of feed (if 5 or less items in feed)
NSUInteger arrayLength = [filteredObjs count];
if (arrayLength <= 5) {
filteredObjs = [filteredObjs filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
return ![obj isKindOfClass:[%c(IGSpinnerLabelViewModel) class]];
}]
];
}
return filteredObjs;
}
%end
%hook IGSundialFeedDataSource
- (NSArray *)objectsForListAdapter:(id)arg1 {
NSArray *filteredList = removeItemsInList(%orig, NO);
if ([SCIUtils getBoolPref:@"prevent_doom_scrolling"]) {
double reelCount = [SCIUtils getDoublePref:@"doom_scrolling_reel_count"];
return [filteredList subarrayWithRange:NSMakeRange(0, MIN((NSUInteger)reelCount, filteredList.count))];
}
return filteredList;
}
%end
%hook IGContextualFeedViewController
- (NSArray *)objectsForListAdapter:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return removeItemsInList(%orig, NO);
}
return %orig;
}
%end
%hook IGVideoFeedViewController
- (NSArray *)objectsForListAdapter:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return removeItemsInList(%orig, NO);
}
return %orig;
}
%end
%hook IGChainingFeedViewController
- (NSArray *)objectsForListAdapter:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return removeItemsInList(%orig, NO);
}
return %orig;
}
%end
%hook IGStoryAdPool
- (id)initWithUserSession:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
%end
%hook IGStoryAdsManager
- (id)initWithUserSession:(id)arg1 storyViewerLoggingContext:(id)arg2 storyFullscreenSectionLoggingContext:(id)arg3 viewController:(id)arg4 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
%end
%hook IGStoryAdsFetcher
- (id)initWithUserSession:(id)arg1 delegate:(id)arg2 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
%end
// IG 148.0
%hook IGStoryAdsResponseParser
- (id)parsedObjectFromResponse:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
- (id)initWithReelStore:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
%end
%hook IGStoryAdsOptInTextView
- (id)initWithBrandedContentStyledString:(id)arg1 sponsoredPostLabel:(id)arg2 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
%end
%hook IGSundialAdsResponseParser
- (id)parsedObjectFromResponse:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
- (id)initWithMediaStore:(id)arg1 userStore:(id)arg2 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
NSLog(@"[SCInsta] Removing ads");
return nil;
}
return %orig;
}
%end
// "Sponsored" posts on discover/search page
%hook IGExploreListKitDataSource
- (NSArray *)objectsForListAdapter:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return removeItemsInList(%orig, NO);
}
return %orig;
}
%end
// Demangled name: IGExploreViewControllerSwift.IGExploreListKitDataSource
%hook _TtC28IGExploreViewControllerSwift26IGExploreListKitDataSource
- (NSArray *)objectsForListAdapter:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return removeItemsInList(%orig, NO);
}
return %orig;
}
%end
// Hide shopping carousel in reel comments
// Demangled name: IGCommentThreadCommerceCarouselPill.IGCommentThreadCommerceCarousel
%hook _TtC35IGCommentThreadCommerceCarouselPill31IGCommentThreadCommerceCarousel
- (id)initWithFrame:(CGRect)frame pillText:(id)text pillStyle:(NSInteger)style {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return nil;
}
return %orig(frame, text, style);
}
%end
// Hide suggested search/shopping on reels
// Demangled name: IGShoppableEverythingCommon.IGRapEntrypointResolver
%hook _TtC27IGShoppableEverythingCommon23IGRapEntrypointResolver
- (id)initWithLauncherSet:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_ads"]) {
return nil;
}
return %orig(arg1);
}
%end
// Demangled name: IGSundialOrganicCTAContainerView.IGSundialOrganicCTAContainerView
%hook _TtC32IGSundialOrganicCTAContainerView32IGSundialOrganicCTAContainerView
- (void)didMoveToWindow {
%orig;
if ([SCIUtils getBoolPref:@"hide_ads"]) {
[self removeFromSuperview];
}
}
%end
// Hide "suggested for you" text at end of feed
%hook IGEndOfFeedDemarcatorCellTopOfFeed
- (void)configureWithViewConfig:(id)arg1 {
%orig;
if ([SCIUtils getBoolPref:@"no_suggested_post"]) {
NSLog(@"[SCInsta] Hiding end of feed message");
// Hide suggested for you text
UILabel *_titleLabel = MSHookIvar<UILabel *>(self, "_titleLabel");
if (_titleLabel != nil) {
[_titleLabel setText:@""];
}
}
return;
}
%end
+15
View File
@@ -0,0 +1,15 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// Disable story data source
%hook IGMainStoryTrayDataSource
- (id)initWithUserSession:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_stories_tray"]) {
NSLog(@"[SCInsta] Hiding story tray");
return nil;
}
return %orig;
}
%end
+15
View File
@@ -0,0 +1,15 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// Remove suggested threads posts (carousel, under suggested posts in feed)
%hook BKBloksViewHelper
- (id)initWithObjectSet:(id)arg1 bloksData:(id)arg2 delegate:(id)arg3 {
if ([SCIUtils getBoolPref:@"no_suggested_threads"]) {
NSLog(@"[SCInsta] Hiding threads posts");
return nil;
}
return %orig;
}
%end
+50
View File
@@ -0,0 +1,50 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
#import "../../../modules/JGProgressHUD/JGProgressHUD.h"
%hook IGCoreTextView
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"copy_description"]) {
[self addHandleLongPress];
}
return;
}
%new - (void)addHandleLongPress {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPress.minimumPressDuration = 0.5;
[self addGestureRecognizer:longPress];
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
// Remove hashtags at end of string
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:@"\\s*(?:#[^\\s]+\\s*)+$"
options:0
error:nil];
NSString *result = [[regex stringByReplacingMatchesInString:self.text
options:0
range:NSMakeRange(0, self.text.length)
withTemplate:@""]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(@"[SCInsta] Copying description");
// Copy text to system clipboard
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = result;
// Notify user
JGProgressHUD *HUD = [[JGProgressHUD alloc] init];
HUD.textLabel.text = @"Copied text to clipboard";
HUD.indicatorView = [[JGProgressHUDSuccessIndicatorView alloc] init];
[HUD showInView:topMostController().view];
[HUD dismissAfterDelay:2.0];
}
%end
@@ -0,0 +1,87 @@
#import "../../InstagramHeaders.h"
#import "../../Utils.h"
%hook IGStoryEyedropperToggleButton
- (void)didMoveToWindow {
%orig;
if ([SCIUtils getBoolPref:@"detailed_color_picker"]) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
if ([self.gestureRecognizers count] == 0) {
NSLog(@"[SCInsta] Adding color eyedroppper long press gesture recognizer");
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPress.minimumPressDuration = 0.25;
[self addGestureRecognizer:longPress];
}
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
UIColorPickerViewController *colorPickerController = [[UIColorPickerViewController alloc] init];
colorPickerController.delegate = (id<UIColorPickerViewControllerDelegate>)self; // cast to suppress warnings
colorPickerController.title = @"Select color";
colorPickerController.modalPresentationStyle = UIModalPresentationPopover;
colorPickerController.supportsAlpha = NO;
colorPickerController.selectedColor = self.color;
UIViewController *presentingVC = [SCIUtils nearestViewControllerForView:self];
if (presentingVC != nil) {
[presentingVC presentViewController:colorPickerController animated:YES completion:nil];
}
}
// UIColorPickerViewControllerDelegate Protocol
%new - (void)colorPickerViewController:(UIColorPickerViewController *)viewController
didSelectColor:(UIColor *)color
continuously:(BOOL)continuously
{
NSLog(@"[SCInsta] Selected text color: %@", color);
UIColor *opaque = [color colorWithAlphaComponent:1.0];
self.color = opaque;
[self setPushedDown:YES];
// Trigger change for text color
id presentingVC = [SCIUtils nearestViewControllerForView:self];
if ([presentingVC isKindOfClass:%c(IGStoryTextEntryViewController)]) {
[presentingVC textViewControllerDidUpdateWithColor:color colorSource:0];
}
else if (
[presentingVC isKindOfClass:%c(IGStoryCreationDrawingViewController)]
|| [presentingVC isKindOfClass:%c(IGDirectThreadViewDrawingViewController)]
) {
[presentingVC drawingControls:nil didSelectColor:color];
}
};
%end
%hook IGStoryColorPaletteView
- (CGFloat)collectionView:(id)view didSelectItemAtIndexPath:(id)index {
UIView *colorPickingControls = [self superview];
if (
[colorPickingControls isKindOfClass:%c(IGStoryColorPickingControls)]
|| [colorPickingControls isKindOfClass:%c(IGDirectThreadColorPickingControls)]
) {
IGStoryEyedropperToggleButton *_eyedropperToggleButton = MSHookIvar<IGStoryEyedropperToggleButton *>(colorPickingControls, "_eyedropperToggleButton");
if (_eyedropperToggleButton != nil) {
[_eyedropperToggleButton setPushedDown:NO];
}
}
return %orig;
}
%end
@@ -0,0 +1,36 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
%hook IGUnifiedVideoCollectionView
- (void)didMoveToWindow {
%orig;
if ([SCIUtils getBoolPref:@"disable_scrolling_reels"]) {
NSLog(@"[SCInsta] Disabling scrolling reels");
self.scrollEnabled = false;
}
}
- (void)setScrollEnabled:(BOOL)arg1 {
if ([SCIUtils getBoolPref:@"disable_scrolling_reels"]) {
NSLog(@"[SCInsta] Disabling scrolling reels");
return %orig(NO);
}
return %orig;
}
%end
// Disable auto-scrolling reels
%hook _TtC19IGSundialAutoScroll19IGSundialAutoScroll
- (void)setIsEnabled:(BOOL)enabled {
if ([SCIUtils getBoolPref:@"disable_scrolling_reels"]) {
%orig(NO);
}
else {
%orig(enabled);
}
}
%end
+31
View File
@@ -0,0 +1,31 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
%hook IGExploreGridViewController
- (void)viewDidLoad {
if ([SCIUtils getBoolPref:@"hide_explore_grid"]) {
NSLog(@"[SCInsta] Hiding explore grid");
[[self view] removeFromSuperview];
return;
}
return %orig;
}
%end
%hook IGExploreViewController
- (void)viewDidLoad {
%orig;
if ([SCIUtils getBoolPref:@"hide_explore_grid"]) {
NSLog(@"[SCInsta] Hiding explore grid");
IGShimmeringGridView *shimmeringGridView = MSHookIvar<IGShimmeringGridView *>(self, "_shimmeringGridView");
if (shimmeringGridView != nil) {
[shimmeringGridView removeFromSuperview];
}
}
}
%end
+33
View File
@@ -0,0 +1,33 @@
#import "../../Utils.h"
%hook IGDirectNotesTrayRowCell
- (id)listAdapterObjects {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"hide_friends_map"]) {
if ([obj isKindOfClass:%c(IGDirectNotesTrayUserViewModel)]) {
if ([[obj valueForKey:@"notePk"] isEqualToString:@"friends_map"]) {
NSLog(@"[SCInsta] Hiding friends map");
shouldHide = YES;
}
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
+572
View File
@@ -0,0 +1,572 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// Direct
// Meta AI button functionality on direct search bar
%hook IGDirectInboxViewController
- (void)searchBarMetaAIButtonTappedOnSearchBar:(id)arg1 {
if ([SCIUtils getBoolPref:@"hide_meta_ai"])
{
NSLog(@"[SCInsta] Hiding meta ai: direct search bar functionality");
return;
}
return %orig;
}
%end
// AI agents in direct new message view
%hook IGDirectRecipientGenAIBotsResult
- (id)initWithGenAIBots:(id)arg1 lastFetchedTimestamp:(id)arg2 {
if ([SCIUtils getBoolPref:@"hide_meta_ai"])
{
NSLog(@"[SCInsta] Hiding meta ai: direct recipient ai agents");
return nil;
}
return %orig;
}
%end
// Meta AI in message composer
%hook IGDirectCommandSystemListViewController
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
if ([obj isKindOfClass:%c(IGDirectCommandSystemViewModel)]) {
IGDirectCommandSystemViewModel *typedObj = (IGDirectCommandSystemViewModel *)obj;
IGDirectCommandSystemRow *cmdSystemRow = (IGDirectCommandSystemRow *)[typedObj row];
IGDirectCommandSystemResult *_commandResult_command = MSHookIvar<IGDirectCommandSystemResult *>(cmdSystemRow, "_commandResult_command");
if (_commandResult_command != nil) {
// Meta AI
if ([[_commandResult_command title] isEqualToString:@"Meta AI"]) {
NSLog(@"[SCInsta] Hiding meta ai: direct message composer suggestion");
shouldHide = YES;
}
// Meta AI (Imagine)
else if ([[_commandResult_command commandString] hasPrefix:@"/imagine"]) {
NSLog(@"[SCInsta] Hiding meta ai: direct message composer /imagine suggestion");
shouldHide = YES;
}
}
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
// Suggested AI chats in direct inbox header
%hook IGDirectInboxNavigationHeaderView
- (id)initWithFrame:(CGRect)arg1
title:(id)arg2
titleView:(id)arg3
directInboxConfig:(IGDirectInboxConfig *)config
userSession:(id)arg5
loggingDelegate:(id)arg6
{
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: suggested ai chats in direct inbox header");
@try {
[config setValue:0 forKey:@"shouldShowAIChatsEntrypointButton"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
}
return %orig(arg1, arg2, arg3, [config copy], arg5, arg6);
}
%end
// Meta AI "imagine" in media picker
%hook IGDirectMediaPickerViewController
- (id)initWithUserSession:(id)arg1
config:(IGDirectMediaPickerConfig *)config
capabilities:(id)arg3
threadMetadata:(id)arg4
messageSender:(id)arg5
threadAnalyticsLogger:(id)arg6
multimodalPerfLogger:(id)arg7
localSendSpeedLogger:(id)arg8
sendAttributionFactory:(id)arg9
{
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: imagine tile in media picker");
@try {
IGDirectMediaPickerGalleryConfig *galleryConfig = [config valueForKey:@"galleryConfig"];
[galleryConfig setValue:0 forKey:@"isImagineEntryPointEnabled"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
}
return %orig(arg1, [config copy], arg3, arg4, arg5, arg6, arg7, arg8, arg9);
}
%end
// Write with meta ai in message composer
%hook IGDirectComposer
- (id)initWithLayoutSpecProvider:(id)arg1
userLauncherSetProviding:(id)arg2
config:(IGDirectComposerConfig *)config
style:(id)arg4
text:(id)arg5
{
return %orig(arg1, arg2, [self patchConfig:config], arg4, arg5);
}
- (id)initWithLayoutSpecProvider:(id)arg1
userLauncherSetProviding:(id)arg2
config:(IGDirectComposerConfig *)config
style:(id)arg4
text:(id)arg5
shouldUpdateModeLater:(BOOL)arg6
{
return %orig(arg1, arg2, [self patchConfig:config], arg4, arg5, arg6);
}
- (void)setConfig:(IGDirectComposerConfig *)config {
%orig([self patchConfig:config]);
return;
}
%new - (IGDirectComposerConfig *)patchConfig:(IGDirectComposerConfig *)config {
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: reconfiguring direct composer");
// writeWithAIEnabled
@try {
[config setValue:0 forKey:@"writeWithAIEnabled"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
}
return [config copy];
}
%end
// Direct sticker tray picker view
%hook IGStickerTrayListAdapterDataSource
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
if ([obj isKindOfClass:%c(IGDirectUnifiedComposerAIStickerModel)]) {
NSLog(@"[SCInsta] Hiding meta ai: AI stickers option in sticker view");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
// Long press menu on messages
// Demangled name: IGDirectMessageMenuConfiguration.IGDirectMessageMenuConfiguration
%hook _TtC32IGDirectMessageMenuConfiguration32IGDirectMessageMenuConfiguration
+ (id)menuConfigurationWithEligibleOptions:(id)options
messageViewModel:(id)arg2
contentType:(id)arg3
isSticker:(_Bool)arg4
isMusicSticker:(_Bool)arg5
directNuxManager:(id)arg6
sessionUserDefaults:(id)arg7
launcherSet:(id)arg8
userSession:(id)arg9
tapHandler:(id)arg10
{
// 31: Restyle
// 41: Make AI image
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", @[ @(31), @(41) ]];
NSArray *newOptions = [options filteredArrayUsingPredicate:predicate];
return %orig([newOptions copy], arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
%end
// Expanded in-chat photo UI
// Demangled name: IGDirectAggregatedMediaViewerComponentsSwift.IGDirectAggregatedMediaViewerViewControllerTitleViewModelObject
%hook _TtC44IGDirectAggregatedMediaViewerComponentsSwift63IGDirectAggregatedMediaViewerViewControllerTitleViewModelObject
- (id)initWithAuthorProfileImage:(id)arg1
authorUsername:(id)arg2
canForward:(_Bool)arg3
canSave:(_Bool)arg4
canAddToStory:(_Bool)arg5
canShowAIRestyle:(_Bool)arg6
canUnsend:(_Bool)arg7
canReport:(_Bool)arg8
displayConfig:(id)arg9
isPending:(_Bool)arg10
isMoreMenuListStyle:(_Bool)arg11
senderIsCurrentUser:(_Bool)arg12
shouldHideInfoViews:(_Bool)arg13
subtitle:(id)arg14
entryPoint:(long long)arg15
canTapAuthor:(_Bool)arg16
{
BOOL showAiRestyle = [SCIUtils getBoolPref:@"hide_meta_ai"] ? false : arg6;
return %orig(arg1, arg2, arg3, arg4, arg5, showAiRestyle, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16);
}
%end
// AI generated DM channel themes
%hook IGDirectThreadThemePickerViewController
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
if (
[obj isKindOfClass:%c(IGDirectThreadThemePickerOption)]
&& [[obj valueForKey:@"themeId"] isEqualToString:@"direct_ai_theme_creation"]
) {
NSLog(@"[SCInsta] Hiding meta ai: AI generated DM channel themes");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
// "Click to summarize" pill under DM navigation bar
%hook IGDirectThreadViewMetaAISummaryFeatureController
- (id)initWithUserSession:(id)arg1 mutableStateProvider:(id)arg2 threadViewControllerFeatureDelegate:(id)arg3 presentingViewController:(id)arg4 {
return nil;
}
%end
/////////////////////////////////////////////////////////////////////////////
// Explore
// Meta AI explore search summary
%hook IGDiscoveryListKitGQLDataSource
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
// Meta AI summary
if ([obj isKindOfClass:%c(IGSearchMetaAIHCMModel)]) {
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding explore meta ai search summary");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
// Meta AI search bar ring button
%hook IGSearchBarDonutButton
- (void)didMoveToWindow {
%orig;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
[self removeFromSuperview];
}
}
%end
/////////////////////////////////////////////////////////////////////////////
// Reels/Sundial
// Suggested AI searches in comment section
%hook IGCommentThreadAICarousel
- (id)initWithLauncherSet:(id)arg1 hasSearchPrefix:(BOOL)arg2 {
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: suggested ai searches comment carousel");
return nil;
}
return %orig;
}
%end
%hook _TtC34IGCommentThreadAICarouselPillSwift30IGCommentThreadAICarouselSwift
- (id)initWithLauncherSet:(id)arg1 hasSearchPrefix:(BOOL)arg2 {
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: suggested ai searches comment carousel");
return nil;
}
return %orig;
}
%end
/////////////////////////////////////////////////////////////////////////////
// Story
// AI images "add to story" suggestion
// Demangled name: IGGalleryDestinationToolbar.IGGalleryDestinationToolbarView
%hook _TtC27IGGalleryDestinationToolbar31IGGalleryDestinationToolbarView
- (void)setTools:(id)tools {
NSArray *newTools = [tools copy];
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) ]];
newTools = [tools filteredArrayUsingPredicate:predicate];
}
%orig(newTools);
return;
}
%end
// AI generated fonts in text entry
%hook IGCreationTextToolView
- (id)initWithMenuConfiguration:(unsigned long long)configuration userSession:(id)session creationEntryPoint:(long long)point isAIFontsEnabled:(_Bool)enabled genAINuxManager:(id)manager showFontBadge:(_Bool)badge {
return %orig(configuration, session, point, [SCIUtils getBoolPref:@"hide_meta_ai"] ? false : enabled, manager, badge);
}
%end
// Text rewrite in text entry
%hook IGStoryTextMentionLocationPickerView
- (id)initWithIsTextRewriteEnabled:(_Bool)arg1
isImageRewriteEnabled:(_Bool)arg2
isStackedToolSelectorEnabled:(_Bool)arg3
isMentionLocationVisible:(_Bool)arg4
isEnabledForFeedCaption:(_Bool)arg5
isFeedEntryPoint:(_Bool)arg6
{
_Bool isTextRewriteEnabled = [SCIUtils getBoolPref:@"hide_meta_ai"] ? false : arg1;
_Bool isImageRewriteEnabled = [SCIUtils getBoolPref:@"hide_meta_ai"] ? false : arg2;
return %orig(isTextRewriteEnabled, isImageRewriteEnabled, arg3, arg4, arg5, arg6);
}
%end
// "Imagine background" in story editor vertical action bar
%hook _TtC17IGCreationOSSwift19IGCreationHeaderBar
- (void)setButtons:(id)buttons maxItems:(NSInteger)max {
NSArray *filteredObjs = buttons;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
filteredObjs = [filteredObjs filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(IGCreationActionBarLabeledButton *obj, NSDictionary *bindings) {
return !(
obj.button
&& [((IGCreationActionBarButton *)obj.button).accessibilityIdentifier isEqualToString:@"contextual-background"]
);
}]
];
}
%orig(filteredObjs, max);
}
%end
/////////////////////////////////////////////////////////////////////////////
// Other
// Meta AI-branded search bars
%hook IGSearchBar
- (id)initWithConfig:(IGSearchBarConfig *)config {
return %orig([self sanitizePlaceholderForConfig:config]);
}
- (id)initWithConfig:(IGSearchBarConfig *)config userSession:(id)arg2 {
return %orig([self sanitizePlaceholderForConfig:config], arg2);
}
- (void)setConfig:(IGSearchBarConfig *)config {
%orig([self sanitizePlaceholderForConfig:config]);
return;
}
%new - (IGSearchBarConfig *)sanitizePlaceholderForConfig:(IGSearchBarConfig *)config {
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: reconfiguring search bar");
NSString *placeholder = [config valueForKey:@"placeholder"];
if ([placeholder containsString:@"Meta AI"]) {
// placeholder
@try {
[config setValue:@"Search" forKey:@"placeholder"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
// shouldAnimatePlaceholder
@try {
[config setValue:0 forKey:@"shouldAnimatePlaceholder"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
NSLog(@"[SCInsta] Changed search bar placeholder from: \"%@\" to \"%@\"", placeholder, [config valueForKey:@"placeholder"]);
// leftIconStyle
@try {
[config setValue:0 forKey:@"leftIconStyle"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
// rightButtonStyle
@try {
[config setValue:0 forKey:@"rightButtonStyle"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, config);
}
}
}
return [config copy];
}
%end
// Themed in-app buttons
%hook IGTapButton
- (void)didMoveToWindow {
%orig;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
// Hide buttons that are associated with meta ai
if ([self.accessibilityIdentifier containsString:@"meta_ai"]) {
NSLog(@"[SCInsta] Hiding meta ai: meta ai associated button");
[self removeFromSuperview];
}
}
}
%end
// Home feed meta ai button
%hook IGFloatingActionButton.IGFloatingActionButton
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
[self removeFromSuperview];
NSLog(@"[SCInsta] Hiding meta ai: home feed meta ai button");
}
}
%end
// Share menu recipients
%hook IGDirectRecipientListViewController
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"hide_meta_ai"]) {
if ([obj isKindOfClass:%c(IGDirectRecipientCellViewModel)]) {
// Meta AI (catch-all)
if ([[[obj recipient] threadName] isEqualToString:@"Meta AI"]) {
NSLog(@"[SCInsta] Hiding meta ai suggested as recipient (share menu)");
shouldHide = YES;
}
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
@@ -0,0 +1,16 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
%hook IGDSSegmentedPillBarView
- (void)didMoveToWindow {
%orig;
if ([[self delegate] isKindOfClass:%c(IGSearchTypeaheadNavigationHeaderView)]) {
if ([SCIUtils getBoolPref:@"hide_trending_searches"]) {
NSLog(@"[SCInsta] Hiding trending searches");
[self removeFromSuperview];
}
}
}
%end
+100
View File
@@ -0,0 +1,100 @@
#import "../../Utils.h"
BOOL isSurfaceShown(IGMainAppSurfaceIntent *surface) {
BOOL isShown = YES;
// Feed
if ([[surface tabStringFromSurfaceIntent] isEqualToString:@"FEED"] && [SCIUtils getBoolPref:@"hide_feed_tab"]) {
isShown = NO;
}
// Reels
else if ([[surface tabStringFromSurfaceIntent] isEqualToString:@"CLIPS"] && [SCIUtils getBoolPref:@"hide_reels_tab"]) {
isShown = NO;
}
// Explore
else if ([[surface tabStringFromSurfaceIntent] isEqualToString:@"SEARCH"] && [SCIUtils getBoolPref:@"hide_explore_tab"]) {
isShown = NO;
}
// Create
else if ([(NSNumber *)[surface valueForKey:@"_subtype"] unsignedIntegerValue] == 3 && [SCIUtils getBoolPref:@"hide_create_tab"]) {
isShown = NO;
}
return isShown;
}
NSArray *filterSurfacesArray(NSArray *surfaces) {
NSMutableArray *filteredSurfaces = [NSMutableArray array];
for (IGMainAppSurfaceIntent *surface in surfaces) {
if (![surface isKindOfClass:%c(IGMainAppSurfaceIntent)]) break;
if (isSurfaceShown(surface)) {
[filteredSurfaces addObject:surface];
}
}
return filteredSurfaces;
}
///////////////////////////////////////////////
%hook IGTabBarControllerSwipeCoordinator
- (id)initWithSurfaces:(id)surfaces parentViewController:(id)controller enableHaptics:(_Bool)haptics launcherSet:(id)set {
// Removes the surface from the main swipeable app collection view
return %orig(filterSurfacesArray(surfaces), controller, haptics, set);
}
%end
%hook IGTabBarController
- (void)_layoutTabBar {
// Prevents the wrong icon from being shown as selected because of mismatched surface array indexes
NSArray *_tabBarSurfaces = [SCIUtils getIvarForObj:self name:"_tabBarSurfaces"];
[SCIUtils setIvarForObj:self name:"_tabBarSurfaces" value:filterSurfacesArray(_tabBarSurfaces)];
%orig;
}
- (id)_buttonForTabBarSurface:(id)surface {
// Prevents the button from being added to the tab bar
id button = %orig(surface);
if (!isSurfaceShown(surface)) {
return nil;
}
return button;
}
%end
// Demangled name: IGNavConfiguration.IGNavConfiguration
%hook _TtC18IGNavConfiguration18IGNavConfiguration
- (NSInteger)tabOrdering {
if ([[SCIUtils getStringPref:@"nav_icon_ordering"] isEqualToString:@"classic"]) return 0;
else if ([[SCIUtils getStringPref:@"nav_icon_ordering"] isEqualToString:@"standard"]) return 1;
else if ([[SCIUtils getStringPref:@"nav_icon_ordering"] isEqualToString:@"alternate"]) return 2;
return %orig;
}
- (void)setTabOrdering:(NSInteger)arg1 {
return;
}
- (BOOL)isTabSwipingEnabled {
if ([[SCIUtils getStringPref:@"swipe_nav_tabs"] isEqualToString:@"enabled"]) return YES;
else if ([[SCIUtils getStringPref:@"swipe_nav_tabs"] isEqualToString:@"disabled"]) return NO;
return %orig;
}
- (void)setIsTabSwipingEnabled:(BOOL)arg1 {
return;
}
%end
+50
View File
@@ -0,0 +1,50 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// Disable logging of searches at server-side
%hook IGSearchEntityRouter
- (id)initWithUserSession:(id)arg1 analyticsModule:(id)arg2 shouldAddToRecents:(BOOL)shouldAddToRecents {
if ([SCIUtils getBoolPref:@"no_recent_searches"]) {
NSLog(@"[SCInsta] Disabling recent searches");
shouldAddToRecents = false;
}
return %orig(arg1, arg2, shouldAddToRecents);
}
%end
// Most in-app search bars
%hook IGRecentSearchStore
- (id)initWithDiskManager:(id)arg1 recentSearchStoreConfiguration:(id)arg2 {
if ([SCIUtils getBoolPref:@"no_recent_searches"]) {
NSLog(@"[SCInsta] Disabling recent searches");
return nil;
}
return %orig;
}
- (BOOL)addItem:(id)arg1 {
if ([SCIUtils getBoolPref:@"no_recent_searches"]) {
NSLog(@"[SCInsta] Disabling recent searches");
return nil;
}
return %orig;
}
%end
// Recent dm message recipients search bar
%hook IGDirectRecipientRecentSearchStorage
- (id)initWithDiskManager:(id)arg1 directCache:(id)arg2 userStore:(id)arg3 currentUser:(id)arg4 featureSets:(id)arg5 {
if ([SCIUtils getBoolPref:@"no_recent_searches"]) {
NSLog(@"[SCInsta] Disabling recent searches");
return nil;
}
return %orig;
}
%end
+19
View File
@@ -0,0 +1,19 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// Channels dms tab (header)
%hook IGDirectInboxHeaderSectionController
- (id)viewModel {
if ([[%orig title] isEqualToString:@"Suggested"]) {
if ([SCIUtils getBoolPref:@"no_suggested_chats"]) {
NSLog(@"[SCInsta] Hiding suggested chats (header: channels tab)");
return nil;
}
}
return %orig;
}
%end
+263
View File
@@ -0,0 +1,263 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// "Welcome to instagram" suggested users in feed
%hook IGSuggestedUnitViewModel
- (id)initWithAYMFModel:(id)arg1 headerViewModel:(id)arg2 {
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
NSLog(@"[SCInsta] Hiding suggested users: main feed welcome section");
return nil;
}
return %orig;
}
%end
%hook IGSuggestionsUnitViewModel
- (id)initWithAYMFModel:(id)arg1 headerViewModel:(id)arg2 {
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
NSLog(@"[SCInsta] Hiding suggested users: main feed welcome section");
return nil;
}
return %orig;
}
%end
// Suggested users in profile header
%hook IGProfileHeaderView
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
if ([obj isKindOfClass:%c(IGProfileChainingModel)]) {
NSLog(@"[SCInsta] Hiding suggested users: profile header");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
// Notifications/activity feed
%hook IGActivityFeedViewController
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig();
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (id obj in originalObjs) {
BOOL shouldHide = NO;
// Section header
if ([obj isKindOfClass:%c(IGLabelItemViewModel)]) {
// Suggested for you
if ([[obj labelTitle] isEqualToString:@"Suggested for you"]) {
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
NSLog(@"[SCInsta] Hiding suggested users (header: activity feed)");
shouldHide = YES;
}
}
}
// Suggested user
else if ([obj isKindOfClass:%c(IGDiscoverPeopleItemConfiguration)]) {
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
NSLog(@"[SCInsta] Hiding suggested users: (user: activity feed)");
shouldHide = YES;
}
}
// "See all" button
else if ([obj isKindOfClass:%c(IGSeeAllItemConfiguration)]) {
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
NSLog(@"[SCInsta] Hiding suggested users: (see all: activity feed)");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
// Profile "following" and "followers" tabs
%hook IGFollowListViewController
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig(arg1);
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (IGStoryTrayViewModel *obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
// Suggested user
if ([obj isKindOfClass:%c(IGDiscoverPeopleItemConfiguration)]) {
NSLog(@"[SCInsta] Hiding suggested users: follow list suggested user");
shouldHide = YES;
}
// Section header
else if ([obj isKindOfClass:%c(IGLabelItemViewModel)]) {
// "Suggested for you" search results header
if ([[obj valueForKey:@"labelTitle"] isEqualToString:@"Suggested for you"]) {
shouldHide = YES;
}
}
// See all suggested users
else if ([obj isKindOfClass:%c(IGSeeAllItemConfiguration)] && ((IGSeeAllItemConfiguration *)obj).destination == 4) {
NSLog(@"[SCInsta] Hiding suggested users: follow list suggested user");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
%hook IGSegmentedTabControl
- (void)setSegments:(id)segments {
NSArray *originalObjs = segments;
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (IGStoryTrayViewModel *obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
if ([obj isKindOfClass:%c(IGFindUsersViewController)]) {
NSLog(@"[SCInsta] Hiding suggested users: find users segmented tab");
shouldHide = YES;
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return %orig([filteredObjs copy]);
}
%end
// Suggested subscriptions
%hook IGFanClubSuggestedUsersDataSource
- (id)initWithUserSession:(id)arg1 delegate:(id)arg2 {
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
return nil;
}
return %orig(arg1, arg2);
}
%end
// Follow request/discover section (accessed through notifications page)
// Demangled name: IGFriendingCenter.IGFriendingCenterViewController
%hook _TtC17IGFriendingCenter31IGFriendingCenterViewController
- (id)objectsForListAdapter:(id)arg1 {
NSArray *originalObjs = %orig(arg1);
NSMutableArray *filteredObjs = [NSMutableArray arrayWithCapacity:[originalObjs count]];
for (IGStoryTrayViewModel *obj in originalObjs) {
BOOL shouldHide = NO;
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
// Suggested user
if ([obj isKindOfClass:%c(IGDiscoverPeopleItemConfiguration)]) {
NSLog(@"[SCInsta] Hiding suggested users: follow list suggested user");
shouldHide = YES;
}
// Section header
else if ([obj isKindOfClass:%c(IGLabelItemViewModel)]) {
// "Suggested for you" search results header
if ([[obj valueForKey:@"labelTitle"] isEqualToString:@"Suggested for you"]) {
shouldHide = YES;
}
}
}
// Populate new objs array
if (!shouldHide) {
[filteredObjs addObject:obj];
}
}
return [filteredObjs copy];
}
%end
%hook IGProfileActionBarViewModel
- (id)initWithIdentifier:(id)arg1
rows:(id)arg2
allActionsToDisplay:(id)arg3
overflowActions:(id)arg4
actionToBadgeInfoMap:(id)arg5
allBusinessActions:(id)arg6
overflowBusinessActions:(id)arg7
contactSheetActions:(id)arg8
user:(id)arg9
sponsoredInfoProvider:(id)arg10
profileBackgroundColor:(id)arg11
{
NSArray *rows = arg2;
NSOrderedSet *allActions = [arg3 copy];
NSOrderedSet *overflowActions = [arg4 copy];
if ([SCIUtils getBoolPref:@"no_suggested_users"]) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", @[ @(3) ]];
// Actions sets
allActions = [allActions filteredOrderedSetUsingPredicate:predicate];
overflowActions = [overflowActions filteredOrderedSetUsingPredicate:predicate];
// Rows of actions sets
NSMutableArray *filteredRows = [NSMutableArray new];
for (NSOrderedSet *set in rows) {
[filteredRows addObject:[set filteredOrderedSetUsingPredicate:predicate]];
}
rows = [filteredRows copy];
}
return %orig(arg1, rows, allActions, overflowActions, arg5, arg6, arg7, arg8, arg9, arg10, arg11);
}
%end
+293
View File
@@ -0,0 +1,293 @@
#import "../../Utils.h"
static char targetStaticRef[] = "target";
%hook IGDirectNotesCreationView
- (id)initWithViewModel:(id)model
featureSupport:(IGNotesCreationFeatureSupportModel *)support
presentationAnimation:(id)animation
composerUpdateListener:(id)listener
delegate:(id)delegate
layoutType:(long long)type
userSession:(id)session
{
if ([SCIUtils getBoolPref:@"enable_notes_customization"]) {
// enableAnimatedEmojisInCreation
@try {
[support setValue:@(YES) forKey:@"enableAnimatedEmojisInCreation"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, support);
}
// enableBubbleCustomization
@try {
[support setValue:@(YES) forKey:@"enableBubbleCustomization"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, support);
}
// enableRandomThemeGenerator
@try {
[support setValue:@(YES) forKey:@"enableRandomThemeGenerator"];
}
@catch (NSException *exception) {
NSLog(@"[SCInsta] WARNING: %@\n\nFull object: %@", exception.reason, support);
}
}
return %orig(model, support, animation, listener, delegate, type, session);
}
%end
// Demangled name: IGDirectNotesUISwift.IGDirectNotesBubbleEditorColorPaletteView
%hook _TtC20IGDirectNotesUISwift41IGDirectNotesBubbleEditorColorPaletteView
%property (nonatomic, copy) UIColor *backgroundColor;
%property (nonatomic, copy) UIColor *textColor;
%property (nonatomic, copy) NSString *emojiText;
- (void)didMoveToWindow {
%orig;
if (![SCIUtils getBoolPref:@"custom_note_themes"]) return;
// Inject buttons once in view lifecycle
static char didInjectButtons;
if (objc_getAssociatedObject(self, &didInjectButtons)) {
return;
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self || !self.window) {
return;
}
UIView *container = self.superview ?: self.window;
if (!container) {
return;
}
// Button config
UIButtonConfiguration *config = [UIButtonConfiguration tintedButtonConfiguration];
config.background.cornerRadius = 12.0;
config.cornerStyle = UIButtonConfigurationCornerStyleFixed;
config.contentInsets = NSDirectionalEdgeInsetsMake(13.7, 10, 13.7, 10);
// Left button
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeSystem];
leftButton.configuration = config;
leftButton.translatesAutoresizingMaskIntoConstraints = NO;
leftButton.tintColor = [SCIUtils SCIColor_Primary];
NSMutableAttributedString *attrTitleLeft = [[NSMutableAttributedString alloc] initWithString:@"Background"];
[attrTitleLeft addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]
range:NSMakeRange(0, attrTitleLeft.length)
];
[leftButton setAttributedTitle:attrTitleLeft forState:UIControlStateNormal];
[leftButton sizeToFit];
[leftButton addAction:[UIAction actionWithHandler:^(__kindof UIAction * _Nonnull action) {
[self presentColorPicker:@"Background"];
}] forControlEvents:UIControlEventTouchUpInside];
// Middle button
UIButton *middleButton = [UIButton buttonWithType:UIButtonTypeSystem];
middleButton.configuration = config;
middleButton.translatesAutoresizingMaskIntoConstraints = NO;
middleButton.tintColor = [SCIUtils SCIColor_Primary];
NSMutableAttributedString *attrTitleMiddle = [[NSMutableAttributedString alloc] initWithString:@"Text"];
[attrTitleMiddle addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]
range:NSMakeRange(0, attrTitleMiddle.length)
];
[middleButton setAttributedTitle:attrTitleMiddle forState:UIControlStateNormal];
[middleButton sizeToFit];
[middleButton addAction:[UIAction actionWithHandler:^(__kindof UIAction * _Nonnull action) {
[self presentColorPicker:@"Text"];
}] forControlEvents:UIControlEventTouchUpInside];
// Right button
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeSystem];
rightButton.configuration = config;
rightButton.translatesAutoresizingMaskIntoConstraints = NO;
rightButton.tintColor = [SCIUtils SCIColor_Primary];
NSMutableAttributedString *attrTitleRight = [[NSMutableAttributedString alloc] initWithString:@"Emoji"];
[attrTitleRight addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]
range:NSMakeRange(0, attrTitleRight.length)
];
[rightButton setAttributedTitle:attrTitleRight forState:UIControlStateNormal];
[rightButton sizeToFit];
[rightButton addAction:[UIAction actionWithHandler:^(__kindof UIAction * _Nonnull action) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Enter Emoji Text"
message:@"Click the Apply button after this to see the emoji"
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"Type emoji...";
}];
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
self.emojiText = alert.textFields[0].text;
[self applySCICustomTheme:@"Emoji"];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
UIViewController *vc = [SCIUtils nearestViewControllerForView:self];
[vc presentViewController:alert animated:YES completion:nil];
}] forControlEvents:UIControlEventTouchUpInside];
// Create stack view
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[leftButton, middleButton, rightButton]];
stack.axis = UILayoutConstraintAxisHorizontal;
stack.spacing = 15.0;
stack.alignment = UIStackViewAlignmentCenter;
stack.distribution = UIStackViewDistributionFillEqually;
// Find max height among arranged subviews
CGFloat maxHeight = 0.0;
for (UIView *subview in stack.arrangedSubviews) {
maxHeight = MAX(maxHeight, subview.bounds.size.height);
}
// Manual frame with side padding
CGFloat bottomMargin = 15.0;
CGRect viewFrame = [self convertRect:self.bounds toView:container];
CGFloat y = CGRectGetMinY(viewFrame) - maxHeight - bottomMargin;
CGFloat width = container.bounds.size.width - stack.spacing * 2;
stack.frame = CGRectMake(stack.spacing, y, width, maxHeight);
[stack layoutIfNeeded];
[container addSubview:stack];
objc_setAssociatedObject(
self,
&didInjectButtons,
@YES,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
});
}
%new - (void)presentColorPicker:(NSString *)target {
UIColorPickerViewController *colorPickerController = [[UIColorPickerViewController alloc] init];
colorPickerController.delegate = (id<UIColorPickerViewControllerDelegate>)self; // cast to suppress warnings
colorPickerController.title = [NSString stringWithFormat:@"%@ color", target];
colorPickerController.modalPresentationStyle = UIModalPresentationPopover;
colorPickerController.supportsAlpha = NO;
// Show last picked color for type
if ([target isEqualToString:@"Background"]) {
colorPickerController.selectedColor = self.backgroundColor;
}
else if ([target isEqualToString:@"Text"]) {
colorPickerController.selectedColor = self.textColor;
}
UIViewController *presentingVC = [SCIUtils nearestViewControllerForView:self];
if (presentingVC != nil) {
[presentingVC presentViewController:colorPickerController animated:YES completion:nil];
}
// Save which color target to update
objc_setAssociatedObject(
presentingVC,
&targetStaticRef,
target,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
// UIColorPickerViewControllerDelegate Protocol
%new - (void)colorPickerViewController:(UIColorPickerViewController *)viewController
didSelectColor:(UIColor *)color
continuously:(BOOL)continuously
{
_TtC20IGDirectNotesUISwift41IGDirectNotesBubbleEditorColorPaletteView *bubbleEditorVC = [SCIUtils nearestViewControllerForView:self];
NSString *target = objc_getAssociatedObject(bubbleEditorVC, &targetStaticRef);
if (!target) return;
// Update saved color target
if ([target isEqualToString:@"Background"]) {
self.backgroundColor = color;
}
else if ([target isEqualToString:@"Text"]) {
self.textColor = color;
}
[self applySCICustomTheme:target];
};
%new - (void)applySCICustomTheme:(NSString *)target {
// Get notes composer vc
_TtC20IGDirectNotesUISwift39IGDirectNotesBubbleEditorViewController *parentVC = [SCIUtils nearestViewControllerForView:self];
if (!parentVC) return;
IGDirectNotesComposerViewController *composerVC = parentVC.delegate;
if (!composerVC) return;
// Get current theme model
IGNotesCustomThemeCreationModel *model = [composerVC valueForKey:@"_selectedCustomThemeCreationModel"];
if (!model) {
// Create new note theme model
model = [[%c(IGNotesCustomThemeCreationModel) alloc] init];
if (!model) return;
}
//SCILog(@"Current note theme model: %@", model);
[model setValue:[composerVC valueForKey:@"_composerText"] forKey:@"customEmoji"];
// Update saved color target
if ([target isEqualToString:@"Background"]) {
[model setValue:self.backgroundColor forKey:@"backgroundColor"];
}
else if ([target isEqualToString:@"Text"]) {
[model setValue:self.textColor forKey:@"textColor"];
[model setValue:self.textColor forKey:@"secondaryTextColor"];
}
// Always set emoji to prevent it being overwritten
[model setValue:self.emojiText forKey:@"customEmoji"];
//SCILog(@"Updated note theme model: %@", model);
// Apply custom notes theme
[composerVC notesBubbleEditorViewControllerDidUpdateWithCustomThemeCreationModel:model];
// Enable apply/cancel buttons
UIView *parentVCView = [parentVC view];
if (!parentVCView) return;
NSArray<UIView *> *parentVCSubviews = [parentVCView subviews];
if (!parentVCSubviews) return;
[parentVCSubviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:%c(IGDSBottomButtonsView)]) {
[obj setPrimaryButtonEnabled:YES];
[obj setSecondaryButtonEnabled:YES];
}
}];
}
%end
@@ -0,0 +1,58 @@
#import "../../InstagramHeaders.h"
#import "../../Settings/SCISettingsViewController.h"
// Show SCInsta tweak settings by holding on the settings/more icon under profile for ~1 second
%hook IGBadgedNavigationButton
- (void)didMoveToWindow {
%orig;
if ([self.accessibilityIdentifier isEqualToString:@"profile-more-button"]) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
if ([self.gestureRecognizers count] == 0) {
NSLog(@"[SCInsta] Adding tweak settings long press gesture recognizer");
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
[self addGestureRecognizer:longPress];
}
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
NSLog(@"[SCInsta] Tweak settings gesture activated");
[SCIUtils showSettingsVC:[self window]];
}
%end
// Quick access to tweak settings by holding on home tab button
%hook IGTabBarButton
- (void)didMoveToSuperview {
%orig;
// Only work on home/feed tab
if (![self.accessibilityIdentifier isEqualToString:@"mainfeed-tab"]) return;
if ([SCIUtils getBoolPref:@"settings_shortcut"]) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPress.minimumPressDuration = 0.3;
// Take precidence over existing gesture recognizers
for (UIGestureRecognizer *existing in self.gestureRecognizers) {
[existing requireGestureRecognizerToFail:longPress];
}
[self addGestureRecognizer:longPress];
}
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
[SCIUtils showSettingsVC:[self window]];
}
%end
+38
View File
@@ -0,0 +1,38 @@
#import "../../InstagramHeaders.h"
#import "../../Utils.h"
%hook IGImageWithAccessoryButton
- (void)didMoveToSuperview {
%orig;
[self addLongPressGestureRecognizer];
}
%new - (void)addLongPressGestureRecognizer {
BOOL hasLongPress = [self.gestureRecognizers filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(NSObject *item, NSDictionary *_) {
return [item isKindOfClass:[UILongPressGestureRecognizer class]];
}]
].count > 0;
if (!hasLongPress) {
NSLog(@"[SCInsta] Adding teen app icons long press gesture recognizer");
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
[self addGestureRecognizer:longPress];
}
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
if ([SCIUtils getBoolPref:@"teen_app_icons"]) {
IGHomeFeedHeaderViewController *homeFeedHeaderVC = [SCIUtils nearestViewControllerForView:self];
if (homeFeedHeaderVC != nil) {
[homeFeedHeaderVC headerDidLongPressLogo:nil];
}
}
}
%end
+703
View File
@@ -0,0 +1,703 @@
#import "../../InstagramHeaders.h"
#import "../../Utils.h"
#import "../../Downloader/Download.h"
#import <objc/runtime.h>
static SCIDownloadDelegate *imageDownloadDelegate;
static SCIDownloadDelegate *videoDownloadDelegate;
static DownloadAction sciGetDownloadAction() {
NSString *method = [SCIUtils getStringPref:@"dw_save_action"];
if ([method isEqualToString:@"photos"]) return saveToPhotos;
return share;
}
static void initDownloaders () {
// Re-init each time to pick up the current save action preference
DownloadAction action = sciGetDownloadAction();
DownloadAction imgAction = (action == saveToPhotos) ? saveToPhotos : quickLook;
imageDownloadDelegate = [[SCIDownloadDelegate alloc] initWithAction:imgAction showProgress:NO];
videoDownloadDelegate = [[SCIDownloadDelegate alloc] initWithAction:action showProgress:YES];
}
// Helper: run a download block with optional confirmation dialog
static void sciConfirmAndDownload(NSString *title, void(^downloadBlock)(void)) {
if ([SCIUtils getBoolPref:@"dw_confirm"]) {
[SCIUtils showConfirmation:downloadBlock title:title];
} else {
downloadBlock();
}
}
// Helper: recursively search within a view tree for downloadable media (bounded to one post)
static BOOL sciFindAndDownloadMediaInView(UIView *root) {
if (!root) return NO;
// Check for video media via mediaCellFeedItem
if ([root respondsToSelector:@selector(mediaCellFeedItem)]) {
IGMedia *media = [root performSelector:@selector(mediaCellFeedItem)];
if (media) {
NSURL *videoUrl = [SCIUtils getVideoUrlForMedia:media];
if (videoUrl) {
initDownloaders();
[videoDownloadDelegate downloadFileWithURL:videoUrl fileExtension:[[videoUrl lastPathComponent] pathExtension] hudLabel:nil];
return YES;
}
NSURL *photoUrl = [SCIUtils getPhotoUrlForMedia:media];
if (photoUrl) {
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl fileExtension:[[photoUrl lastPathComponent] pathExtension] hudLabel:nil];
return YES;
}
}
}
// Check for IGFeedPhotoView with delegate chain
if ([root isKindOfClass:NSClassFromString(@"IGFeedPhotoView")] && [root respondsToSelector:@selector(delegate)]) {
id delegate = [root performSelector:@selector(delegate)];
if ([delegate isKindOfClass:NSClassFromString(@"IGFeedItemPhotoCell")]) {
@try {
Ivar cfgIvar = class_getInstanceVariable([delegate class], "_configuration");
if (cfgIvar) {
id cfg = object_getIvar(delegate, cfgIvar);
if (cfg) {
Ivar photoIvar = class_getInstanceVariable([cfg class], "_photo");
if (photoIvar) {
IGPhoto *photo = object_getIvar(cfg, photoIvar);
NSURL *photoUrl = [SCIUtils getPhotoUrl:photo];
if (photoUrl) {
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl fileExtension:[[photoUrl lastPathComponent] pathExtension] hudLabel:nil];
return YES;
}
}
}
}
} @catch (NSException *e) {}
}
if ([delegate isKindOfClass:NSClassFromString(@"IGFeedItemPagePhotoCell")]) {
@try {
if ([delegate respondsToSelector:@selector(pagePhotoPost)]) {
id pagePhotoPost = [delegate performSelector:@selector(pagePhotoPost)];
if (pagePhotoPost && [pagePhotoPost respondsToSelector:@selector(photo)]) {
IGPhoto *photo = [pagePhotoPost performSelector:@selector(photo)];
NSURL *photoUrl = [SCIUtils getPhotoUrl:photo];
if (photoUrl) {
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl fileExtension:[[photoUrl lastPathComponent] pathExtension] hudLabel:nil];
return YES;
}
}
}
} @catch (NSException *e) {}
}
}
// Recurse into subviews
for (UIView *sub in root.subviews) {
if (sciFindAndDownloadMediaInView(sub)) return YES;
}
return NO;
}
// Helper: find IGMedia from a cell using runtime ivar scanning
// Avoids property getters which can cause EXC_BAD_ACCESS on certain IG versions
static IGMedia * _Nullable sciGetMediaFromView(UIView *view) {
if (!view) return nil;
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList([view class], &ivarCount);
if (!ivars) return nil;
IGMedia *found = nil;
Class mediaClass = NSClassFromString(@"IGMedia");
for (unsigned int i = 0; i < ivarCount; i++) {
const char *name = ivar_getName(ivars[i]);
if (!name) continue;
NSString *ivarName = [NSString stringWithUTF8String:name];
NSString *lower = [ivarName lowercaseString];
if ([lower containsString:@"video"] || [lower containsString:@"media"] || [lower containsString:@"item"]) {
id value = object_getIvar(view, ivars[i]);
if (value && mediaClass && [value isKindOfClass:mediaClass]) {
found = (IGMedia *)value;
NSLog(@"[SCInsta] Found IGMedia in ivar '%@' of %@", ivarName, NSStringFromClass([view class]));
break;
}
}
}
free(ivars);
return found;
}
// Helper: walk superview chain to find a view of a given class
static UIView * _Nullable sciFindSuperviewOfClass(UIView *view, NSString *className) {
Class cls = NSClassFromString(className);
if (!cls) return nil;
UIView *current = view.superview;
int depth = 0;
while (current && depth < 15) {
if ([current isKindOfClass:cls]) return current;
current = current.superview;
depth++;
}
return nil;
}
// Helper: show debug ivar dump when media extraction fails (survives IG updates)
static void sciShowDebugIvarDump(UIView *cell) {
NSMutableString *debug = [NSMutableString stringWithFormat:@"No IGMedia found in %@\n\nIvars:\n", NSStringFromClass([cell class])];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([cell class], &count);
for (unsigned int i = 0; i < count && i < 50; i++) {
const char *name = ivar_getName(ivars[i]);
const char *type = ivar_getTypeEncoding(ivars[i]);
if (name) [debug appendFormat:@"%s (%s)\n", name, type ? type : "?"];
}
if (ivars) free(ivars);
NSLog(@"[SCInsta] Debug: %@", debug);
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SCInsta Debug"
message:debug
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Copy & Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction *a) {
[[UIPasteboard generalPasteboard] setString:debug];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:nil]];
UIViewController *topVC = topMostController();
if (topVC) [topVC presentViewController:alert animated:YES completion:nil];
});
}
// Whether download buttons (not long-press) are enabled
static BOOL sciUseDownloadButtons() {
return [[SCIUtils getStringPref:@"dw_method"] isEqualToString:@"button"];
}
/* * Feed * */
// Download feed images
%hook IGFeedPhotoView
- (void)didMoveToSuperview {
%orig;
if (![SCIUtils getBoolPref:@"dw_feed_posts"]) return;
if (sciUseDownloadButtons()) {
[self sciAddDownloadButton];
} else {
[self addLongPressGestureRecognizer];
}
}
%new - (void)sciAddDownloadButton {
if ([self viewWithTag:1338]) return;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.tag = 1338;
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium];
[btn setImage:[UIImage systemImageNamed:@"arrow.down.to.line" withConfiguration:config] forState:UIControlStateNormal];
btn.tintColor = [UIColor whiteColor];
btn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4];
btn.layer.cornerRadius = 16;
btn.clipsToBounds = YES;
btn.translatesAutoresizingMaskIntoConstraints = NO;
[btn addTarget:self action:@selector(sciDownloadBtnTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
[NSLayoutConstraint activateConstraints:@[
[btn.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:10],
[btn.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-10],
[btn.widthAnchor constraintEqualToConstant:32],
[btn.heightAnchor constraintEqualToConstant:32]
]];
}
%new - (void)sciDownloadBtnTapped:(UIButton *)sender {
UIImpactFeedbackGenerator *haptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[haptic impactOccurred];
[UIView animateWithDuration:0.1 animations:^{ sender.transform = CGAffineTransformMakeScale(0.75, 0.75); }
completion:^(BOOL f) { [UIView animateWithDuration:0.1 animations:^{ sender.transform = CGAffineTransformIdentity; }]; }];
sciConfirmAndDownload(@"Download photo?", ^{
[self handleLongPress:nil];
});
}
%new - (void)addLongPressGestureRecognizer {
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)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender && sender.state != UIGestureRecognizerStateBegan) return;
IGPhoto *photo;
if ([self.delegate isKindOfClass:%c(IGFeedItemPhotoCell)]) {
IGFeedItemPhotoCellConfiguration *_configuration = MSHookIvar<IGFeedItemPhotoCellConfiguration *>(self.delegate, "_configuration");
if (!_configuration) return;
photo = MSHookIvar<IGPhoto *>(_configuration, "_photo");
}
else if ([self.delegate isKindOfClass:%c(IGFeedItemPagePhotoCell)]) {
IGFeedItemPagePhotoCell *pagePhotoCell = self.delegate;
photo = pagePhotoCell.pagePhotoPost.photo;
}
NSURL *photoUrl = [SCIUtils getPhotoUrl:photo];
if (!photoUrl) {
[SCIUtils showErrorHUDWithDescription:@"Could not extract photo url from post"];
return;
}
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl
fileExtension:[[photoUrl lastPathComponent]pathExtension]
hudLabel:nil];
}
%end
// Download feed videos
%hook IGModernFeedVideoCell.IGModernFeedVideoCell
- (void)didMoveToSuperview {
%orig;
if (![SCIUtils getBoolPref:@"dw_feed_posts"]) return;
if (sciUseDownloadButtons()) {
[self sciAddDownloadButton];
} else {
[self addLongPressGestureRecognizer];
}
}
%new - (void)sciAddDownloadButton {
UIView *selfView = (UIView *)self;
if ([selfView viewWithTag:1338]) return;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.tag = 1338;
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium];
[btn setImage:[UIImage systemImageNamed:@"arrow.down.to.line" withConfiguration:config] forState:UIControlStateNormal];
btn.tintColor = [UIColor whiteColor];
btn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4];
btn.layer.cornerRadius = 16;
btn.clipsToBounds = YES;
btn.translatesAutoresizingMaskIntoConstraints = NO;
[btn addTarget:self action:@selector(sciDownloadBtnTapped:) forControlEvents:UIControlEventTouchUpInside];
[selfView addSubview:btn];
[NSLayoutConstraint activateConstraints:@[
[btn.leadingAnchor constraintEqualToAnchor:selfView.leadingAnchor constant:10],
[btn.bottomAnchor constraintEqualToAnchor:selfView.bottomAnchor constant:-10],
[btn.widthAnchor constraintEqualToConstant:32],
[btn.heightAnchor constraintEqualToConstant:32]
]];
}
%new - (void)sciDownloadBtnTapped:(UIButton *)sender {
UIImpactFeedbackGenerator *haptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[haptic impactOccurred];
[UIView animateWithDuration:0.1 animations:^{ sender.transform = CGAffineTransformMakeScale(0.75, 0.75); }
completion:^(BOOL f) { [UIView animateWithDuration:0.1 animations:^{ sender.transform = CGAffineTransformIdentity; }]; }];
sciConfirmAndDownload(@"Download video?", ^{
[self handleLongPress:nil];
});
}
%new - (void)addLongPressGestureRecognizer {
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)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender && sender.state != UIGestureRecognizerStateBegan) return;
NSURL *videoUrl = [SCIUtils getVideoUrlForMedia:[self mediaCellFeedItem]];
if (!videoUrl) {
[SCIUtils showErrorHUDWithDescription:@"Could not extract video url from post"];
return;
}
initDownloaders();
[videoDownloadDelegate downloadFileWithURL:videoUrl
fileExtension:[[videoUrl lastPathComponent] pathExtension]
hudLabel:nil];
}
%end
/* * Reels * */
// Download reels (photos) — long press only when gesture mode selected
%hook IGSundialViewerPhotoView
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"dw_reels"] && !sciUseDownloadButtons()) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
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)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
@try {
IGPhoto *_photo = nil;
@try {
_photo = MSHookIvar<IGPhoto *>(self, "_photo");
} @catch (NSException *e) {}
if (!_photo) {
[SCIUtils showErrorHUDWithDescription:@"Could not access reel photo"];
return;
}
NSURL *photoUrl = [SCIUtils getPhotoUrl:_photo];
if (!photoUrl) {
[SCIUtils showErrorHUDWithDescription:@"Could not extract photo url from reel"];
return;
}
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl
fileExtension:[[photoUrl lastPathComponent]pathExtension]
hudLabel:nil];
} @catch (NSException *exception) {
NSLog(@"[SCInsta] Reel photo download error: %@", exception);
[SCIUtils showErrorHUDWithDescription:[NSString stringWithFormat:@"Reel photo download failed: %@", exception.reason]];
}
}
%end
// Download reels (videos) — long press only when gesture mode selected
%hook IGSundialViewerVideoCell
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"dw_reels"] && !sciUseDownloadButtons()) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
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)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
@try {
IGMedia *media = sciGetMediaFromView(self);
if (!media) {
[SCIUtils showErrorHUDWithDescription:@"Could not access reel media"];
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];
} @catch (NSException *exception) {
NSLog(@"[SCInsta] Reel download error: %@", exception);
[SCIUtils showErrorHUDWithDescription:[NSString stringWithFormat:@"Reel download failed: %@", exception.reason]];
}
}
%end
// Download button on reels vertical UFI (like/comment/share sidebar)
%hook IGSundialViewerVerticalUFI
- (void)didMoveToSuperview {
%orig;
if (![SCIUtils getBoolPref:@"dw_reels"]) return;
if (!sciUseDownloadButtons()) return;
if (!self.superview) return;
// Add to superview so we're not clipped by the narrow 29pt UFI
UIView *parent = self.superview;
if ([parent viewWithTag:1337]) return;
UIButton *downloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
downloadBtn.tag = 1337;
// Match IG reel sidebar style: outline icon, semi-transparent white
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:24 weight:UIImageSymbolWeightSemibold];
UIImage *icon = [UIImage systemImageNamed:@"arrow.down" withConfiguration:config];
[downloadBtn setImage:icon forState:UIControlStateNormal];
downloadBtn.tintColor = [UIColor colorWithWhite:1.0 alpha:0.9];
downloadBtn.layer.shadowColor = [UIColor blackColor].CGColor;
downloadBtn.layer.shadowOffset = CGSizeMake(0, 1);
downloadBtn.layer.shadowOpacity = 0.5;
downloadBtn.layer.shadowRadius = 3;
downloadBtn.translatesAutoresizingMaskIntoConstraints = NO;
[downloadBtn addTarget:self action:@selector(sciDownloadTapped:) forControlEvents:UIControlEventTouchUpInside];
[parent addSubview:downloadBtn];
[NSLayoutConstraint activateConstraints:@[
[downloadBtn.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
[downloadBtn.bottomAnchor constraintEqualToAnchor:self.topAnchor constant:-10],
[downloadBtn.widthAnchor constraintEqualToConstant:40],
[downloadBtn.heightAnchor constraintEqualToConstant:40]
]];
}
%new - (void)sciDownloadTapped:(UIButton *)sender {
NSLog(@"[SCInsta] Reel download button tapped");
// Haptic + visual feedback
UIImpactFeedbackGenerator *haptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[haptic impactOccurred];
[UIView animateWithDuration:0.1 animations:^{
sender.transform = CGAffineTransformMakeScale(0.75, 0.75);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
sender.transform = CGAffineTransformIdentity;
}];
}];
sciConfirmAndDownload(@"Download reel?", ^{
// Find IGSundialViewerVideoCell in superview chain
UIView *videoCell = sciFindSuperviewOfClass(self, @"IGSundialViewerVideoCell");
if (videoCell) {
IGMedia *media = sciGetMediaFromView(videoCell);
if (media) {
NSURL *videoUrl = [SCIUtils getVideoUrlForMedia:media];
if (videoUrl) {
initDownloaders();
[videoDownloadDelegate downloadFileWithURL:videoUrl
fileExtension:[[videoUrl lastPathComponent] pathExtension]
hudLabel:nil];
return;
}
[SCIUtils showErrorHUDWithDescription:@"Could not extract video URL from reel"];
return;
}
sciShowDebugIvarDump(videoCell);
return;
}
// Try photo reel
UIView *photoView = sciFindSuperviewOfClass(self, @"IGSundialViewerPhotoView");
if (photoView) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([photoView class], &count);
Class photoClass = NSClassFromString(@"IGPhoto");
for (unsigned int i = 0; i < count; i++) {
const char *name = ivar_getName(ivars[i]);
if (!name) continue;
NSString *ivarName = [NSString stringWithUTF8String:name];
if ([[ivarName lowercaseString] containsString:@"photo"]) {
id value = object_getIvar(photoView, ivars[i]);
if (value && photoClass && [value isKindOfClass:photoClass]) {
NSURL *photoUrl = [SCIUtils getPhotoUrl:(IGPhoto *)value];
if (photoUrl) {
free(ivars);
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl
fileExtension:[[photoUrl lastPathComponent] pathExtension]
hudLabel:nil];
return;
}
}
}
}
if (ivars) free(ivars);
sciShowDebugIvarDump(photoView);
return;
}
[SCIUtils showErrorHUDWithDescription:@"Could not find reel cell in view hierarchy"];
});
}
%end
/* * Stories * */
// Download story (images)
%hook IGStoryPhotoView
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"dw_story"]) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
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)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
NSURL *photoUrl = [SCIUtils getPhotoUrlForMedia:[self item]];
if (!photoUrl) {
[SCIUtils showErrorHUDWithDescription:@"Could not extract photo url from story"];
return;
}
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:photoUrl
fileExtension:[[photoUrl lastPathComponent]pathExtension]
hudLabel:nil];
}
%end
// Download story (videos)
%hook IGStoryModernVideoView
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"dw_story"]) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
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)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
NSURL *videoUrl = [SCIUtils getVideoUrlForMedia:self.item];
if (!videoUrl) {
[SCIUtils showErrorHUDWithDescription:@"Could not extract video url from story"];
return;
}
initDownloaders();
[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];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
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)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<IGDirectVisualMessageViewerViewModeAwareDataSource *>(parentVC, "_dataSource");
if (!_dataSource) return;
IGDirectVisualMessage *_currentMessage = MSHookIvar<IGDirectVisualMessage *>(_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;
}
initDownloaders();
[videoDownloadDelegate downloadFileWithURL:videoUrl
fileExtension:[[videoUrl lastPathComponent] pathExtension]
hudLabel:nil];
}
%end
/* * Profile pictures * */
%hook IGProfilePictureImageView
- (void)didMoveToSuperview {
%orig;
if ([SCIUtils getBoolPref:@"save_profile"]) {
[self addLongPressGestureRecognizer];
}
return;
}
%new - (void)addLongPressGestureRecognizer {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
[self addGestureRecognizer:longPress];
}
%new - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) return;
IGImageView *_imageView = MSHookIvar<IGImageView *>(self, "_imageView");
if (!_imageView) return;
IGImageSpecifier *imageSpecifier = _imageView.imageSpecifier;
if (!imageSpecifier) return;
NSURL *imageUrl = imageSpecifier.url;
if (!imageUrl) return;
initDownloaders();
[imageDownloadDelegate downloadFileWithURL:imageUrl
fileExtension:[[imageUrl lastPathComponent] pathExtension]
hudLabel:@"Loading"];
}
%end
+13
View File
@@ -0,0 +1,13 @@
#import "../../Utils.h"
%hook IGSundialViewerNavigationBarOld
- (void)didMoveToWindow {
%orig;
if ([SCIUtils getBoolPref:@"hide_reels_header"]) {
NSLog(@"[SCInsta] Hiding reels header");
[self removeFromSuperview];
}
}
%end
+72
View File
@@ -0,0 +1,72 @@
#import "../../Utils.h"
%hook IGSundialPlaybackControlsTestConfiguration
- (id)initWithLauncherSet:(id)set
tapToPauseEnabled:(_Bool)tapPauseEnabled
combineSingleTapPlaybackControls:(_Bool)controls
isVideoPreviewThumbnailEnabled:(_Bool)previewThumbEnabled
minScrubberDurationSec:(long long)minSec
seekResumeScrubberCooldownSec:(double)seekSec
tapResumeScrubberCooldownSec:(double)tapSec
persistentScrubberMinVideoDuration:(long long)duration
isScrubberForShortVideoEnabled:(_Bool)shortScrubberEnabled
{
_Bool userTapPauseEnabled = tapPauseEnabled;
if ([[SCIUtils getStringPref:@"reels_tap_control"] isEqualToString:@"pause"]) userTapPauseEnabled = true;
else if ([[SCIUtils getStringPref:@"reels_tap_control"] isEqualToString:@"mute"]) userTapPauseEnabled = false;
long long userMinSec = minSec;
long long userDuration = duration;
_Bool userShortScrubberEnabled = shortScrubberEnabled;
if ([SCIUtils getBoolPref:@"reels_show_scrubber"]) {
userMinSec = 0;
userDuration = 0;
userShortScrubberEnabled = true;
}
return %orig(set, userTapPauseEnabled, controls, previewThumbEnabled, userMinSec, seekSec, tapSec, userDuration, userShortScrubberEnabled);
}
%end
%hook IGSundialFeedViewController
- (void)_refreshReelsWithParamsForNetworkRequest:(NSInteger)arg1 userDidPullToRefresh:(BOOL)arg2 {
if ([SCIUtils getBoolPref:@"prevent_doom_scrolling"]) {
IGRefreshControl *_refreshControl = MSHookIvar<IGRefreshControl *>(self, "_refreshControl");
[self refreshControlDidEndFinishLoadingAnimation:_refreshControl];
return;
}
if ([SCIUtils getBoolPref:@"refresh_reel_confirm"]) {
NSLog(@"[SCInsta] Reel refresh triggered");
[SCIUtils showConfirmation:^(void) { %orig(arg1, arg2); }
cancelHandler:^(void) {
IGRefreshControl *_refreshControl = MSHookIvar<IGRefreshControl *>(self, "_refreshControl");
[self refreshControlDidEndFinishLoadingAnimation:_refreshControl];
}
title:@"Refresh Reels"];
} else {
return %orig(arg1, arg2);
}
}
%end
// * Disable volume/mute button triggering unmutes
%hook IGAudioStatusAnnouncer
- (void)_muteSwitchStateChanged:(id)changed {
if (![SCIUtils getBoolPref:@"disable_auto_unmuting_reels"]) {
%orig(changed);
}
}
- (void)_didPressVolumeButton:(id)button {
if (![SCIUtils getBoolPref:@"disable_auto_unmuting_reels"]) {
%orig(button);
}
}
- (void)_didUnplugHeadphones:(id)headphones {
if (![SCIUtils getBoolPref:@"disable_auto_unmuting_reels"]) {
%orig(headphones);
}
}
%end
@@ -0,0 +1,58 @@
#import "../../Utils.h"
#define QUICKSNAPENABLED(orig) return [SCIUtils getBoolPref:@"disable_instants_creation"] ? false : orig;
// Demangled name: IGQuickSnapExperimentation.IGQuickSnapExperimentationHelper
%hook _TtC26IGQuickSnapExperimentation32IGQuickSnapExperimentationHelper
+ (_Bool)isQuicksnapEnabled:(id)enabled {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapEnabledInFeed:(id)feed {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapEnabledInInbox:(id)inbox {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapEnabledInStories:(id)stories {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapEnabledInNotesTray:(id)tray {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapEnabledInNotesTrayWithPeek:(id)peek {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapEnabledInNotesTrayWithPog:(id)pog {
QUICKSNAPENABLED(%orig);
}
+ (_Bool)isQuicksnapNotesTrayEmptyPogEnabled:(id)enabled {
QUICKSNAPENABLED(%orig);
}
// + (_Bool)isStoriesSpringEnabled:(id)enabled {
// return true;
// }
// + (_Bool)shouldEnableScreenshotBlocking:(id)blocking {
// return false;
// }
// + (_Bool)areFiltersEnabled:(id)enabled {
// return true;
// }
// + (_Bool)isBottomsheetCustomAudienceEnabled:(id)enabled {
// return true;
// }
// + (_Bool)isVideoCaptureEnabled:(id)enabled {
// return true;
// }
%end
// %hook IGDirectNotesTrayRowCell
// - (_Bool)isQuicksnapPeekVisible {
// return true;
// }
// %end
// %hook IGDirectNotesTrayRowSectionController
// - (_Bool)isQuicksnapPeekVisible {
// return true;
// }
// %end
@@ -0,0 +1,245 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
// Bypass flag: when YES, all hooks let calls through (for manual mark as seen)
static BOOL sciSeenBypassActive = NO;
static BOOL sciShouldBlockSeen() {
if (sciSeenBypassActive) return NO;
return [SCIUtils getBoolPref:@"no_seen_receipt"];
}
// Block story seen receipts by intercepting all known upload/send paths
%hook IGStorySeenStateUploader
- (id)initWithUserSessionPK:(id)arg1 networker:(id)arg2 {
if (sciShouldBlockSeen()) {
NSLog(@"[SCInsta] Blocked story seen uploader init");
return nil;
}
return %orig;
}
- (void)uploadSeenStateWithMedia:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)uploadSeenState {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)_uploadSeenState:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)sendSeenReceipt:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (id)networker {
if (sciShouldBlockSeen()) return nil;
return %orig;
}
%end
// Block seen tracking on fullscreen section controller
%hook IGStoryFullscreenSectionController
- (void)markItemAsSeen:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)_markItemAsSeen:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)storySeenStateDidChange:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)sendSeenRequestForCurrentItem {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)markCurrentItemAsSeen {
if (sciShouldBlockSeen()) return;
%orig;
}
%end
// Block seen on viewer controller
%hook IGStoryViewerViewController
- (void)markAsSeen {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)markStoryAsSeen:(id)arg1 {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)_markCurrentStoryAsSeen {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)markCurrentMediaAsSeen {
if (sciShouldBlockSeen()) return;
%orig;
}
%end
// Block local visual seen state updates on the story tray
// This prevents the colored ring from turning grey after viewing
%hook IGStoryTrayViewModel
- (void)markAsSeen {
if (sciShouldBlockSeen()) return;
%orig;
}
- (void)setHasUnseenMedia:(BOOL)arg1 {
if (sciShouldBlockSeen()) {
// Always keep as unseen visually
%orig(YES);
return;
}
%orig;
}
- (BOOL)hasUnseenMedia {
if (sciShouldBlockSeen()) return YES;
return %orig;
}
- (void)setIsSeen:(BOOL)arg1 {
if (sciShouldBlockSeen()) {
%orig(NO);
return;
}
%orig;
}
- (BOOL)isSeen {
if (sciShouldBlockSeen()) return NO;
return %orig;
}
%end
// Also try to block on the story item model level
%hook IGStoryItem
- (void)setHasSeen:(BOOL)arg1 {
if (sciShouldBlockSeen()) {
%orig(NO);
return;
}
%orig;
}
- (BOOL)hasSeen {
if (sciShouldBlockSeen()) return NO;
return %orig;
}
%end
// Manual "mark as seen" button on story overlay
%hook IGStoryFullscreenOverlayView
- (void)didMoveToSuperview {
%orig;
if (!sciShouldBlockSeen()) return;
if ([self viewWithTag:1339]) return;
UIButton *seenBtn = [UIButton buttonWithType:UIButtonTypeCustom];
seenBtn.tag = 1339;
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:14 weight:UIImageSymbolWeightMedium];
UIImage *icon = [UIImage systemImageNamed:@"eye" withConfiguration:config];
[seenBtn setImage:icon forState:UIControlStateNormal];
[seenBtn setTitle:@" Mark seen" forState:UIControlStateNormal];
[seenBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
seenBtn.titleLabel.font = [UIFont systemFontOfSize:11 weight:UIFontWeightMedium];
seenBtn.tintColor = [UIColor whiteColor];
seenBtn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4];
seenBtn.layer.cornerRadius = 14;
seenBtn.clipsToBounds = YES;
seenBtn.contentEdgeInsets = UIEdgeInsetsMake(6, 10, 6, 12);
seenBtn.translatesAutoresizingMaskIntoConstraints = NO;
[seenBtn addTarget:self action:@selector(sciMarkSeenTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:seenBtn];
// Bottom right, moved up to avoid overlapping existing buttons
[NSLayoutConstraint activateConstraints:@[
[seenBtn.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor constant:-110],
[seenBtn.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-12],
[seenBtn.heightAnchor constraintEqualToConstant:28]
]];
}
%new - (void)sciMarkSeenTapped:(UIButton *)sender {
// Haptic feedback
UIImpactFeedbackGenerator *haptic = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[haptic impactOccurred];
// Visual feedback
[UIView animateWithDuration:0.1 animations:^{
sender.transform = CGAffineTransformMakeScale(0.85, 0.85);
sender.alpha = 0.6;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.15 animations:^{
sender.transform = CGAffineTransformIdentity;
sender.alpha = 1.0;
}];
}];
// Enable bypass so all our hooks let the calls through
sciSeenBypassActive = YES;
BOOL didMark = NO;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// Try all view controllers in responder chain
UIResponder *responder = self;
while (responder) {
// IGStoryViewerViewController
if ([responder isKindOfClass:NSClassFromString(@"IGStoryViewerViewController")]) {
SEL selectors[] = {
@selector(markAsSeen), @selector(markStoryAsSeen:),
@selector(_markCurrentStoryAsSeen), @selector(markCurrentMediaAsSeen)
};
for (int i = 0; i < 4; i++) {
if ([responder respondsToSelector:selectors[i]]) {
NSLog(@"[SCInsta] Manual seen: calling %@ on IGStoryViewerViewController", NSStringFromSelector(selectors[i]));
if (selectors[i] == @selector(markStoryAsSeen:)) {
[responder performSelector:selectors[i] withObject:nil];
} else {
[responder performSelector:selectors[i]];
}
didMark = YES;
}
}
}
// IGStoryFullscreenSectionController (might be in responder chain as next responder of a child VC)
if ([responder isKindOfClass:NSClassFromString(@"IGStoryFullscreenSectionController")]) {
SEL selectors[] = {
@selector(markItemAsSeen:), @selector(markCurrentItemAsSeen),
@selector(sendSeenRequestForCurrentItem)
};
for (int i = 0; i < 3; i++) {
if ([responder respondsToSelector:selectors[i]]) {
NSLog(@"[SCInsta] Manual seen: calling %@ on IGStoryFullscreenSectionController", NSStringFromSelector(selectors[i]));
if (selectors[i] == @selector(markItemAsSeen:)) {
[responder performSelector:selectors[i] withObject:nil];
} else {
[responder performSelector:selectors[i]];
}
didMark = YES;
}
}
}
responder = [responder nextResponder];
}
#pragma clang diagnostic pop
// Re-enable blocking
sciSeenBypassActive = NO;
if (didMark) {
[SCIUtils showToastForDuration:1.5 title:@"Marked as seen"];
} else {
[SCIUtils showToastForDuration:2.0 title:@"Could not mark as seen" subtitle:@"Method not found"];
}
}
%end
@@ -0,0 +1,9 @@
#import "../../Utils.h"
%hook IGDirectTypingStatusService
- (void)updateOutgoingStatusIsActive:(_Bool)active threadKey:(id)key threadMetadata:(id)metadata typingStatusType:(long long)type {
if ([SCIUtils getBoolPref:@"disable_typing_status"]) return;
return %orig(active, key, metadata, type);
}
%end
@@ -0,0 +1,22 @@
#import "../../Utils.h"
#import "../../InstagramHeaders.h"
%hook IGDirectRealtimeIrisThreadDelta
+ (id)removeItemWithMessageId:(id)arg1 {
if ([SCIUtils getBoolPref:@"keep_deleted_message"]) {
arg1 = NULL;
}
return %orig(arg1);
}
%end
%hook IGDirectMessageUpdate
+ (id)removeMessageWithMessageId:(id)arg1{
if ([SCIUtils getBoolPref:@"keep_deleted_message"]) {
arg1 = NULL;
}
return %orig(arg1);
}
%end
@@ -0,0 +1,96 @@
#import "../../InstagramHeaders.h"
#import "../../Tweak.h"
#import "../../Utils.h"
// Seen buttons (in DMs)
// - Enables no seen for messages
// - Enables unlimited views of DM visual messages
%hook IGTallNavigationBarView
- (void)setRightBarButtonItems:(NSArray <UIBarButtonItem *> *)items {
NSMutableArray *new_items = [[items filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(UIView *value, NSDictionary *_) {
if ([SCIUtils getBoolPref:@"hide_reels_blend"]) {
return ![value.accessibilityIdentifier isEqualToString:@"blend-button"];
}
return true;
}]
] mutableCopy];
// Messages seen
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];
}
// DM visual messages viewed
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];
if (dmVisualMsgsViewedButtonEnabled) {
[dmVisualMsgsViewedButton setTintColor:SCIUtils.SCIColor_Primary];
} else {
[dmVisualMsgsViewedButton setTintColor:UIColor.labelColor];
}
}
%orig([new_items copy]);
}
// Messages seen button
%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];
[SCIUtils showToastForDuration:4.5 title:@"Visual messages can be replayed without expiring"];
}
else {
dmVisualMsgsViewedButtonEnabled = true;
[sender setTintColor:SCIUtils.SCIColor_Primary];
[SCIUtils showToastForDuration:4.5 title:@"Visual messages will now expire after viewing"];
}
}
%end
// Messages seen logic
%hook IGDirectThreadViewListAdapterDataSource
- (BOOL)shouldUpdateLastSeenMessage {
if ([SCIUtils getBoolPref:@"remove_lastseen"]) {
return false;
}
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 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;
}
}
}
%end
@@ -0,0 +1,21 @@
#import "../../Utils.h"
%hook IGDirectVisualMessage
- (NSInteger)viewMode {
NSInteger mode = %orig;
// * Modes *
// 0 - View Once
// 1 - Replayable
if ([SCIUtils getBoolPref:@"disable_view_once_limitations"]) {
if (mode == 0) {
mode = 1;
NSLog(@"[SCInsta] Modifying visual message from read-once to replayable");
}
}
return mode;
}
%end