mirror of
https://github.com/faroukbmiled/RyukGram.git
synced 2026-06-08 00:13:54 +02:00
86eaa95019
### Features - **Open Instagram links in app (Safari extension)** — bundled Safari web extension (sideload IPA only). Enable in Safari → Extensions; instagram.com links open in the app. - **Localization** — every user-facing string flows through a central translation layer. Globe button in Settings; missing keys fall back to English. Ships English only — see the "Translating RyukGram" section in the README to add more. - **Action buttons** — context-aware menus on feed, reels, and stories (expand, repost, download, copy caption, etc.) with per-context default tap action and carousel/multi-story bulk download - **Enhanced HD downloads** — up to 1080p via DASH + FFmpegKit with quality picker, preview playback, encoding-speed options, and 720p fallback - **Repost**, **media viewer**, **media zoom** (long-press), **download pill** (frosted glass, stacks concurrent downloads) - **Fake location** — overrides CoreLocation app-wide, map picker + saved presets, optional quick-toggle button on the Friends Map - **Messages-only mode** — strips every tab except DM inbox + profile - **Launch tab** — pick which tab the app opens to - Full last active date in DMs — show full date instead of "Active 2h ago" - Custom date format — 12 formats with per-surface toggles (feed, notes/comments/stories, DMs) - Send files in DMs (experimental) - View story mentions - Hide suggested stories - Story tray long-press actions — view HD profile picture from the tray menu - Advance on story reply — auto-skip to next story after sending a reply or reaction - Mark story as seen on reply or emoji reaction - Hide metrics (likes, comments, shares counts) - Hide messages tab - Hide voice/video call buttons in DM thread header (independent toggles) - Disable app haptics - Disable reels tab refresh - Disable disappearing messages mode in DMs - Follow indicator — shows whether the profile user follows you - Copy note text on long press - Zoom profile photo — long press opens full-screen viewer - Notes actions — copy text, download GIF/audio from notes long-press menu - Confirm unfollow - Feed refresh controls — disable background refresh, home button refresh, and home button scroll ### Improvements - Default tap action: added copy URL, repost, and view mentions options; dynamic menu generation per context - Settings pages reordered: General → Feed → Stories → Reels → Messages → Profile → Navigation → Saving → Confirmations - Fake location picker: native Apple Maps-style UI (search, long-press to drop pin, current location) - Liquid glass floating tab bar + dynamic sizing - Upload audio: FFmpegKit re-encode + trim for any audio/video input - Settings reorganized with per-context action button config; new Profile page - Highlight cover: full-screen viewer replaces direct download - Switched HD encoder to `h264_videotoolbox` (hardware) — no GPL FFmpegKit required - Legacy long-press download deprecated (off by default), replaced by action buttons ### Fixes - Hide suggested stories no longer removes followed users' stories on scroll - Settings search bar transparency with liquid glass off; auto-deactivates on push - HD download cancel: tapping pill aborts in-flight downloads + FFmpeg sessions cleanly - Download pill stuck state on background/foreground, progress reset per download - Disappearing messages mode confirmation not firing on swipe - Detailed color picker not working on story draw `†` - DM seen toggle menu not updating after tap - Reel refresh confirmation appearing on first app launch `†` - Reels action button displacing profile pictures on photo reels - Disappearing DM media download (expand, share, save to Photos with progress pill) - Carousel "Download all" not showing item count in feed - Encoding speed setting being ignored for HD downloads - Various upstream SCInsta merges (Meta AI hiding, suggested chats hiding, notes tray) — marked `†` > `†` Merged from upstream [SCInsta](https://github.com/SoCuul/SCInsta) by SoCuul ### Credits - Thanks to [@erupts0](https://github.com/erupts0) (John) for testing and feature suggestions - Thanks to [@euoradan](https://t.me/euoradan) (Radan) for experimental Instagram feature flag research - Safari extension forked/cleaned from [BillyCurtis/OpenInstagramSafariExtension](https://github.com/BillyCurtis/OpenInstagramSafariExtension) ### Known Issues - Preserved unsent messages can't be removed via "Delete for you"; pull-to-refresh clears them (warning available in settings) - "Delete for you" detection uses a ~2s window after the local action — a real unsend landing in that window may be missed (rare)
293 lines
12 KiB
Plaintext
293 lines
12 KiB
Plaintext
#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:SCILocalized(@"Enter Emoji Text")
|
|
message:SCILocalized(@"Click the Apply button after this to see the emoji")
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = SCILocalized(@"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:SCILocalized(@"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 |