diff --git a/README.md b/README.md index 1780131..cd20cab 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Free & Open Source Tweak for Twitter app on iOS! ## Features - Remove Ads +- Can Save Video (long press on video to save) - Lightweight - Support iOS 10 (not tested) - 11 (tested) - 12 (tested) - 13 (tested) - Support latest Twitter version (If it doesn't work, you should update the app to latest version) diff --git a/Tweak.h b/Tweak.h index b9308ee..f773cc4 100644 --- a/Tweak.h +++ b/Tweak.h @@ -1,4 +1,5 @@ #import +#import #define PLIST_PATH "/var/mobile/Library/Preferences/com.haoict.twitternoadspref.plist" #define PREF_CHANGED_NOTIF "com.haoict.twitternoadspref/PrefChanged" @@ -6,3 +7,46 @@ @interface TFNItemsDataViewController : NSObject - (id)itemAtIndexPath:(id)arg1; @end + +@interface TFSTwitterEntityMediaVideoVariant : NSObject +@property(readonly, nonatomic) long long bitrate; +@property(readonly, copy, nonatomic) NSString *contentType; +@property(readonly, copy, nonatomic) NSString *url; +@end + +@interface TFSTwitterEntityMediaVideoInfo : NSObject +@property(readonly, copy, nonatomic) NSArray *variants; +@end + +@interface TFSTwitterEntityMedia : NSObject +@property(readonly, nonatomic) TFSTwitterEntityMediaVideoInfo *videoInfo; +@end + +@interface TFSTwitterMediaInfo : NSObject +@property(retain, nonatomic) TFSTwitterEntityMedia *mediaEntity; +@end + +@protocol T1StatusViewModel +@property(readonly, nonatomic) TFSTwitterMediaInfo *primaryMediaInfo; +@end + +@interface TFNTwitterStatus : NSObject +@property (nonatomic, readonly, strong) NSString *primaryMediaVideoURL; +@end + +// @interface T1StatusInlineMediaViewModel : NSObject +// @property(readonly, nonatomic) id viewModel; +// @end + +// @interface T1InlineMediaContainerView : UIView +// @property (nonatomic, readonly, strong) UIViewController *tfn_viewController; +// @end + +// @interface T1InlineMediaView : UIView +// @property(readonly, nonatomic) UILongPressGestureRecognizer *longPressGestureRecognizer; +// @property(retain, nonatomic) T1StatusInlineMediaViewModel *viewModel; +// @end + +@interface T1SlideshowViewController : UIViewController +@property (nonatomic, readonly, strong) id slideStatus; +@end diff --git a/Tweak.xm b/Tweak.xm index 24381c4..8e7fc4f 100644 --- a/Tweak.xm +++ b/Tweak.xm @@ -4,41 +4,95 @@ * Load Preferences */ BOOL noads; +BOOL canSaveVideo; static void reloadPrefs() { NSDictionary *settings = [[NSMutableDictionary alloc] initWithContentsOfFile:@PLIST_PATH] ?: [@{} mutableCopy]; noads = [[settings objectForKey:@"noads"] ?: @(YES) boolValue]; + canSaveVideo = [[settings objectForKey:@"canSaveVideo"] ?: @(YES) boolValue]; +} + +static void showDownloadPopup(id twStatus, UIViewController *viewController, void (^origHandler)(UIAlertAction *)) { + UIAlertController* alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet]; + [alert addAction:[UIAlertAction actionWithTitle:@"Save Video" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + NSString* videoUrl = nil; + if ([twStatus isKindOfClass:%c(TFNTwitterStatus)]) { + videoUrl = ((TFNTwitterStatus *)twStatus).primaryMediaVideoURL; + } + // if primary video is m3u8 format, we can't save it, thus we have to look for another place to find .mp4 and save highest bitrate + if (!videoUrl || [videoUrl containsString:@".m3u8"]) { + long long bitrate = 0; + for (TFSTwitterEntityMediaVideoVariant *video in ((id)twStatus).primaryMediaInfo.mediaEntity.videoInfo.variants) { + if ([video.contentType isEqualToString:@"video/mp4"] && video.bitrate > bitrate) { + bitrate = video.bitrate; + videoUrl = video.url; + } + } + } + [HDownloadMedia checkPermissionToPhotosAndDownload:videoUrl appendExtension:nil mediaType:Video toAlbum:@"Twitter"]; + }]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Twitter Options" style:UIAlertActionStyleDefault handler:origHandler]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [viewController presentViewController:alert animated:YES completion:nil]; } %group NoAds %hook TFNItemsDataViewController - - (id)tableViewCellForItem:(id)arg1 atIndexPath:(id)arg2 { - UITableViewCell *tbvCell = %orig; - id item = [self itemAtIndexPath: arg2]; - if ([item respondsToSelector: @selector(isPromoted)] && [item performSelector:@selector(isPromoted)]) { - [tbvCell setHidden: YES]; + - (id)tableViewCellForItem:(id)arg1 atIndexPath:(id)arg2 { + UITableViewCell *tbvCell = %orig; + id item = [self itemAtIndexPath: arg2]; + if ([item respondsToSelector: @selector(isPromoted)] && [item performSelector:@selector(isPromoted)]) { + [tbvCell setHidden: YES]; + } + return tbvCell; } - return tbvCell; - } - - (double)tableView:(id)arg1 heightForRowAtIndexPath:(id)arg2 { - id item = [self itemAtIndexPath: arg2]; - if ([item respondsToSelector: @selector(isPromoted)] && [item performSelector:@selector(isPromoted)]) { - return 0; + - (double)tableView:(id)arg1 heightForRowAtIndexPath:(id)arg2 { + id item = [self itemAtIndexPath: arg2]; + if ([item respondsToSelector: @selector(isPromoted)] && [item performSelector:@selector(isPromoted)]) { + return 0; + } + return %orig; + } + %end +%end + +%group SaveVideo + %hook T1SlideshowViewController + - (void)imageDisplayView:(id)arg1 didLongPress:(id)arg2 { + %orig; + } + + - (void)slideshowSeekController:(id)arg1 didLongPressWithRecognizer:(id)arg2 { + id twStatus = self.slideStatus; + showDownloadPopup(twStatus, self, ^(UIAlertAction *action) { %orig; }); } - return %orig; - } %end + + // doesn't work for ios 13 since it has haptic touch + // %hook T1InlineMediaView + // - (void)_t1_longPressAction { + // if (self.longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) { + // UIViewController *vc = ((T1InlineMediaContainerView *)self.superview).tfn_viewController; + // id twStatus = self.viewModel.viewModel; + // showDownloadPopup(twStatus, vc, ^(UIAlertAction *action) { %orig; }); + // } else { + // %orig; + // } + // } + // %end %end %ctor { CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback) reloadPrefs, CFSTR(PREF_CHANGED_NOTIF), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); reloadPrefs(); - if (!noads) { - return; + if (noads) { + %init(NoAds); } - %init(NoAds); + if (canSaveVideo) { + %init(SaveVideo); + } } diff --git a/control b/control index 842b074..460d042 100644 --- a/control +++ b/control @@ -1,12 +1,13 @@ Package: com.haoict.twitternoads Name: Twitter No Ads -Depends: mobilesubstrate, preferenceloader, com.haoict.libhdev, firmware (>= 10) +Depends: mobilesubstrate, preferenceloader, com.haoict.libhdev (>= 2.0.0), firmware (>= 10) Version: 1.0.0 Architecture: iphoneos-arm Description: Free & Open Source Tweak for Twitter Free & Open Source Tweak for Twitter Features: - Remove Ads + - Can Save Video (long press on video to save) - Lightweight - Support iOS 10 (not tested) - 11 (tested) - 12 (tested) - 13 (tested) - Support latest Twitter version (If it doesn't work, you should update the app to latest version) diff --git a/pref/Resources/Root.plist b/pref/Resources/Root.plist index 1a3fd66..34950f5 100755 --- a/pref/Resources/Root.plist +++ b/pref/Resources/Root.plist @@ -54,6 +54,25 @@ com.haoict.twitternoadspref/PrefChanged + + cell + PSSwitchCell + cellClass + HPSSwitchCell + default + + defaults + com.haoict.twitternoadspref + key + canSaveVideo + label + CAN_SAVE_VIDEO + subtitle + LONG_PRESS_ON_VIDEO_TO_SAVE + PostNotification + com.haoict.twitternoadspref/PrefChanged + + cell diff --git a/pref/Resources/base.lproj/Root.strings b/pref/Resources/base.lproj/Root.strings index 868a0e7..9f1a658 100644 --- a/pref/Resources/base.lproj/Root.strings +++ b/pref/Resources/base.lproj/Root.strings @@ -7,6 +7,8 @@ "MAIN_PREFERENCES" = "Main Preferences"; "NO_ADS" = "No Ads"; "REMOVE_ADS_IN_NEWS_FEED" = "Remove Ads in News Feed"; + "CAN_SAVE_VIDEO" = "Can Save Video"; + "LONG_PRESS_ON_VIDEO_TO_SAVE" = "Long press on video to save"; "OTHER_PREFERENCES" = "Other Preferences"; "RESET_SETTINGS" = "Reset Settings"; "SUPPORT_ME" = "Support Me"; diff --git a/pref/Resources/en.lproj/Root.strings b/pref/Resources/en.lproj/Root.strings index 868a0e7..9f1a658 100644 --- a/pref/Resources/en.lproj/Root.strings +++ b/pref/Resources/en.lproj/Root.strings @@ -7,6 +7,8 @@ "MAIN_PREFERENCES" = "Main Preferences"; "NO_ADS" = "No Ads"; "REMOVE_ADS_IN_NEWS_FEED" = "Remove Ads in News Feed"; + "CAN_SAVE_VIDEO" = "Can Save Video"; + "LONG_PRESS_ON_VIDEO_TO_SAVE" = "Long press on video to save"; "OTHER_PREFERENCES" = "Other Preferences"; "RESET_SETTINGS" = "Reset Settings"; "SUPPORT_ME" = "Support Me"; diff --git a/pref/Resources/vi.lproj/Root.strings b/pref/Resources/vi.lproj/Root.strings index c223735..4e2aed4 100644 --- a/pref/Resources/vi.lproj/Root.strings +++ b/pref/Resources/vi.lproj/Root.strings @@ -6,6 +6,8 @@ "DEFAULT" = "Mặc định"; "MAIN_PREFERENCES" = "Tùy chỉnh chính"; "NO_ADS" = "Gỡ quảng cáo"; + "CAN_SAVE_VIDEO" = "Có thể tải video"; + "LONG_PRESS_ON_VIDEO_TO_SAVE" = "Ấn và giữ video để lưu"; "REMOVE_ADS_IN_NEWS_FEED" = "Gỡ quảng cáo ở News Feed"; "OTHER_PREFERENCES" = "Tùy chỉnh phụ"; "RESET_SETTINGS" = "Đặt lại tùy chỉnh"; diff --git a/release/com.haoict.twitternoads_1.0.0_iphoneos-arm.deb b/release/com.haoict.twitternoads_1.0.0_iphoneos-arm.deb new file mode 100644 index 0000000..87913c5 Binary files /dev/null and b/release/com.haoict.twitternoads_1.0.0_iphoneos-arm.deb differ