Files
youtubeios/YTLite.x
T
2024-02-28 15:02:46 +03:00

1461 lines
63 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "YTLite.h"
static UIImage *YTImageNamed(NSString *imageName) {
return [UIImage imageNamed:imageName inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil];
}
// YouTube-X (https://github.com/PoomSmart/YouTube-X/)
// Background Playback
%hook YTIPlayabilityStatus
- (BOOL)isPlayableInBackground { return kBackgroundPlayback ? YES : NO; }
%end
%hook MLVideo
- (BOOL)playableInBackground { return kBackgroundPlayback ? YES : NO; }
%end
// Disable Ads
%hook YTIPlayerResponse
- (BOOL)isMonetized { return kNoAds ? NO : YES; }
%end
%hook YTDataUtils
+ (id)spamSignalsDictionary { return kNoAds ? nil : %orig; }
+ (id)spamSignalsDictionaryWithoutIDFA { return kNoAds ? nil : %orig; }
%end
%hook YTAdsInnerTubeContextDecorator
- (void)decorateContext:(id)context { if (!kNoAds) %orig; }
%end
%hook YTAccountScopedAdsInnerTubeContextDecorator
- (void)decorateContext:(id)context { if (!kNoAds) %orig; }
%end
%hook YTIElementRenderer
- (NSData *)elementData {
if (self.hasCompatibilityOptions && self.compatibilityOptions.hasAdLoggingData && kNoAds) return nil;
NSString *description = [self description];
NSArray *ads = @[@"brand_promo", @"product_carousel", @"product_engagement_panel", @"product_item", @"text_search_ad", @"text_image_button_layout", @"carousel_headered_layout", @"carousel_footered_layout", @"square_image_layout", @"landscape_image_wide_button_layout", @"feed_ad_metadata"];
if (kNoAds && [ads containsObject:description]) {
return [NSData data];
}
NSArray *shortsToRemove = @[@"shorts_shelf.eml", @"shorts_video_cell.eml", @"6Shorts"];
for (NSString *shorts in shortsToRemove) {
if (kHideShorts && [description containsString:shorts] && ![description containsString:@"history*"]) {
return nil;
}
}
return %orig;
}
%end
%hook YTSectionListViewController
- (void)loadWithModel:(YTISectionListRenderer *)model {
if (kNoAds) {
NSMutableArray <YTISectionListSupportedRenderers *> *contentsArray = model.contentsArray;
NSIndexSet *removeIndexes = [contentsArray indexesOfObjectsPassingTest:^BOOL(YTISectionListSupportedRenderers *renderers, NSUInteger idx, BOOL *stop) {
YTIItemSectionRenderer *sectionRenderer = renderers.itemSectionRenderer;
YTIItemSectionSupportedRenderers *firstObject = [sectionRenderer.contentsArray firstObject];
return firstObject.hasPromotedVideoRenderer || firstObject.hasCompactPromotedVideoRenderer || firstObject.hasPromotedVideoInlineMutedRenderer;
}];
[contentsArray removeObjectsAtIndexes:removeIndexes];
} %orig;
}
%end
// NOYTPremium (https://github.com/PoomSmart/NoYTPremium)
// Alert
%hook YTCommerceEventGroupHandler
- (void)addEventHandlers {}
%end
// Full-screen
%hook YTInterstitialPromoEventGroupHandler
- (void)addEventHandlers {}
%end
%hook YTPromosheetEventGroupHandler
- (void)addEventHandlers {}
%end
%hook YTPromoThrottleController
- (BOOL)canShowThrottledPromo { return NO; }
- (BOOL)canShowThrottledPromoWithFrequencyCap:(id)arg1 { return NO; }
- (BOOL)canShowThrottledPromoWithFrequencyCaps:(id)arg1 { return NO; }
%end
%hook YTIShowFullscreenInterstitialCommand
- (BOOL)shouldThrottleInterstitial { return YES; }
%end
// "Try new features" in settings
%hook YTSettingsSectionItemManager
- (void)updatePremiumEarlyAccessSectionWithEntry:(id)arg1 {}
%end
// Survey
%hook YTSurveyController
- (void)showSurveyWithRenderer:(id)arg1 surveyParentResponder:(id)arg2 {}
%end
// Navbar Stuff
// Disable Cast
%hook MDXPlaybackRouteButtonController
- (BOOL)isPersistentCastIconEnabled { return kNoCast ? NO : YES; }
- (void)updateRouteButton:(id)arg1 { if (!kNoCast) %orig; }
- (void)updateAllRouteButtons { if (!kNoCast) %orig; }
%end
%hook YTSettings
- (void)setDisableMDXDeviceDiscovery:(BOOL)arg1 { %orig(kNoCast); }
%end
// Hide Navigation Bar Buttons
%hook YTRightNavigationButtons
- (void)layoutSubviews {
%orig;
if (kNoNotifsButton) self.notificationButton.hidden = YES;
if (kNoSearchButton) self.searchButton.hidden = YES;
for (UIView *subview in self.subviews) {
if (kNoVoiceSearchButton && [subview.accessibilityLabel isEqualToString:NSLocalizedString(@"search.voice.access", nil)]) subview.hidden = YES;
if (kNoCast && [subview.accessibilityIdentifier isEqualToString:@"id.mdx.playbackroute.button"]) subview.hidden = YES;
}
}
%end
%hook YTSearchViewController
- (void)viewDidLoad {
%orig;
if (kNoVoiceSearchButton) [self setValue:@(NO) forKey:@"_isVoiceSearchAllowed"];
}
- (void)setSuggestions:(id)arg1 { if (!kNoSearchHistory) %orig; }
%end
%hook YTPersonalizedSuggestionsCacheProvider
- (id)activeCache { return kNoSearchHistory ? nil : %orig; }
%end
// Remove Videos Section Under Player
%hook YTWatchNextResultsViewController
- (void)setVisibleSections:(NSInteger)arg1 {
arg1 = (kNoRelatedWatchNexts) ? 1 : arg1;
%orig(arg1);
}
%end
%hook YTHeaderView
// Stick Navigation bar
- (BOOL)stickyNavHeaderEnabled { return kStickyNavbar ? YES : %orig; }
// Hide YouTube Logo
- (void)setCustomTitleView:(UIView *)customTitleView { if (!kNoYTLogo) %orig; }
- (void)setTitle:(NSString *)title { kNoYTLogo ? %orig(@"") : %orig; }
%end
// Remove Subbar
%hook YTMySubsFilterHeaderView
- (void)setChipFilterView:(id)arg1 { if (!kNoSubbar) %orig; }
%end
%hook YTHeaderContentComboView
- (void)enableSubheaderBarWithView:(id)arg1 { if (!kNoSubbar) %orig; }
- (void)setFeedHeaderScrollMode:(int)arg1 { kNoSubbar ? %orig(0) : %orig; }
%end
%hook YTChipCloudCell
- (void)layoutSubviews {
if (self.superview && kNoSubbar) {
[self removeFromSuperview];
} %orig;
}
%end
// Hide Autoplay Switch and Subs Button
%hook YTMainAppControlsOverlayView
- (void)setAutoplaySwitchButtonRenderer:(id)arg1 { if (!kHideAutoplay) %orig; }
- (void)setClosedCaptionsOrSubtitlesButtonAvailable:(BOOL)arg1 { kHideSubs ? %orig(NO) : %orig; }
%end
// Remove HUD Messages
%hook YTHUDMessageView
- (id)initWithMessage:(id)arg1 dismissHandler:(id)arg2 { return kNoHUDMsgs ? nil : %orig; }
%end
%hook YTColdConfig
// Hide Next & Previous buttons
- (BOOL)removeNextPaddleForSingletonVideos { return kHidePrevNext ? YES : %orig; }
- (BOOL)removePreviousPaddleForSingletonVideos { return kHidePrevNext ? YES : %orig; }
// Replace Next & Previous with Fast Forward & Rewind buttons
- (BOOL)replaceNextPaddleWithFastForwardButtonForSingletonVods { return kReplacePrevNext ? YES : %orig; }
- (BOOL)replacePreviousPaddleWithRewindButtonForSingletonVods { return kReplacePrevNext ? YES : %orig; }
// Disable Free Zoom
- (BOOL)videoZoomFreeZoomEnabledGlobalConfig { return kNoFreeZoom ? NO : %orig; }
// Stick Sort Buttons in Comments Section
- (BOOL)enableHideChipsInTheCommentsHeaderOnScrollIos { return kStickSortComments ? NO : %orig; }
// Hide Sort Buttons in Comments Section
- (BOOL)enableChipsInTheCommentsHeaderIos { return kHideSortComments ? NO : %orig; }
// Use System Theme
- (BOOL)shouldUseAppThemeSetting { return YES; }
// Dismiss Panel By Swiping in Fullscreen Mode
- (BOOL)isLandscapeEngagementPanelSwipeRightToDismissEnabled { return YES; }
// Remove Video in Playlist By Swiping To The Right
- (BOOL)enableSwipeToRemoveInPlaylistWatchEp { return YES; }
// Enable Old-style Minibar For Playlist Panel
- (BOOL)queueClientGlobalConfigEnableFloatingPlaylistMinibar { return kPlaylistOldMinibar ? NO : %orig; }
%end
// Remove Dark Background in Overlay
%hook YTMainAppVideoPlayerOverlayView
- (void)setBackgroundVisible:(BOOL)arg1 isGradientBackground:(BOOL)arg2 { kNoDarkBg ? %orig(NO, arg2) : %orig; }
%end
// No Endscreen Cards
%hook YTCreatorEndscreenView
- (void)setHidden:(BOOL)arg1 { kEndScreenCards ? %orig(YES) : %orig; }
%end
// Disable Fullscreen Actions
%hook YTFullscreenActionsView
- (BOOL)enabled { return kNoFullscreenActions ? NO : YES; }
- (void)setEnabled:(BOOL)arg1 { kNoFullscreenActions ? %orig(NO) : %orig; }
%end
// Dont Show Related Videos on Finish
%hook YTFullscreenEngagementOverlayController
- (void)setRelatedVideosVisible:(BOOL)arg1 { kNoRelatedVids ? %orig(NO) : %orig; }
%end
// Hide Paid Promotion Cards
%hook YTMainAppVideoPlayerOverlayViewController
- (void)setPaidContentWithPlayerData:(id)data { if (!kNoPromotionCards) %orig; }
- (void)playerOverlayProvider:(YTPlayerOverlayProvider *)provider didInsertPlayerOverlay:(YTPlayerOverlay *)overlay {
if ([[overlay overlayIdentifier] isEqualToString:@"player_overlay_paid_content"] && kNoPromotionCards) return;
%orig;
}
%end
%hook YTInlineMutedPlaybackPlayerOverlayViewController
- (void)setPaidContentWithPlayerData:(id)data { if (!kNoPromotionCards) %orig; }
%end
%hook YTInlinePlayerBarContainerView
- (void)setPlayerBarAlpha:(CGFloat)alpha { kPersistentProgressBar ? %orig(1.0) : %orig; }
%end
// Remove Watermarks
%hook YTAnnotationsViewController
- (void)loadFeaturedChannelWatermark { if (!kNoWatermarks) %orig; }
%end
%hook YTMainAppVideoPlayerOverlayView
- (BOOL)isWatermarkEnabled { return kNoWatermarks ? NO : %orig; }
%end
// Forcibly Enable Miniplayer
%hook YTWatchMiniBarViewController
- (void)updateMiniBarPlayerStateFromRenderer { if (!kMiniplayer) %orig; }
%end
// Portrait Fullscreen
%hook YTWatchViewController
- (unsigned long long)allowedFullScreenOrientations { return kPortraitFullscreen ? UIInterfaceOrientationMaskAllButUpsideDown : %orig; }
%end
// Disable Autoplay
%hook YTPlaybackConfig
- (void)setStartPlayback:(BOOL)arg1 { kDisableAutoplay ? %orig(NO) : %orig; }
%end
// Skip Content Warning (https://github.com/qnblackcat/uYouPlus/blob/main/uYouPlus.xm#L452-L454)
%hook YTPlayabilityResolutionUserActionUIController
- (void)showConfirmAlert { kNoContentWarning ? [self confirmAlertDidPressConfirm] : %orig; }
%end
// Classic Video Quality (https://github.com/PoomSmart/YTClassicVideoQuality)
%hook YTVideoQualitySwitchControllerFactory
- (id)videoQualitySwitchControllerWithParentResponder:(id)responder {
Class originalClass = %c(YTVideoQualitySwitchOriginalController);
return kClassicQuality && originalClass ? [[originalClass alloc] initWithParentResponder:responder] : %orig;
}
%end
// Extra Speed Options
%hook YTVarispeedSwitchController
- (void)setDelegate:(id)arg1 {
NSMutableArray *optionsCopy = [[self valueForKey:@"_options"] mutableCopy];
NSArray *speedOptions = @[@"2.5", @"3", @"3.5", @"4", @"5"];
for (NSString *title in speedOptions) {
float rate = [title floatValue];
YTVarispeedSwitchControllerOption *option = [[%c(YTVarispeedSwitchControllerOption) alloc] initWithTitle:title rate:rate];
[optionsCopy addObject:option];
}
if (kExtraSpeedOptions) [self setValue:[optionsCopy copy] forKey:@"_options"];
return %orig;
}
%end
// Temprorary Fix For 'Classic Video Quality' and 'Extra Speed Options'
%hook YTVersionUtils
+ (NSString *)appVersion {
NSString *originalVersion = %orig;
NSString *fakeVersion = @"18.18.2";
return (!kClassicQuality && !kExtraSpeedOptions && [originalVersion compare:fakeVersion options:NSNumericSearch] == NSOrderedDescending) ? originalVersion : fakeVersion;
}
%end
// Show real version in YT Settings
%hook YTSettingsCell
- (void)setDetailText:(id)arg1 {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *appVersion = infoDictionary[@"CFBundleShortVersionString"];
if ([arg1 isEqualToString:@"18.18.2"]) {
arg1 = appVersion;
} %orig(arg1);
}
%end
// Disable Snap To Chapter (https://github.com/qnblackcat/uYouPlus/blob/main/uYouPlus.xm#L457-464)
%hook YTSegmentableInlinePlayerBarView
- (void)didMoveToWindow { %orig; if (kDontSnapToChapter) self.enableSnapToChapter = NO; }
%end
// Red Progress Bar and Gray Buffer Progress
%hook YTInlinePlayerBarContainerView
- (id)quietProgressBarColor { return kRedProgressBar ? [UIColor redColor] : %orig; }
%end
%hook YTSegmentableInlinePlayerBarView
- (void)setBufferedProgressBarColor:(id)arg1 { if (kRedProgressBar) %orig([UIColor colorWithRed:0.65 green:0.65 blue:0.65 alpha:0.60]); }
%end
// Disable Hints
%hook YTSettings
- (BOOL)areHintsDisabled { return kNoHints ? YES : NO; }
- (void)setHintsDisabled:(BOOL)arg1 { kNoHints ? %orig(YES) : %orig; }
%end
%hook YTUserDefaults
- (BOOL)areHintsDisabled { return kNoHints ? YES : NO; }
- (void)setHintsDisabled:(BOOL)arg1 { kNoHints ? %orig(YES) : %orig; }
%end
// Enter Fullscreen on Start (https://github.com/PoomSmart/YTAutoFullScreen)
%hook YTPlayerViewController
- (void)loadWithPlayerTransition:(id)arg1 playbackConfig:(id)arg2 {
%orig;
if (kWiFiQualityIndex != 0 || kCellQualityIndex != 0) [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(autoQuality) userInfo:nil repeats:NO];
if (kAutoFullscreen) [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(autoFullscreen) userInfo:nil repeats:NO];
if (kShortsToRegular) [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(shortsToRegular) userInfo:nil repeats:NO];
if (kDisableAutoCaptions) [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(turnOffCaptions) userInfo:nil repeats:NO];
}
%new
- (void)autoFullscreen {
YTWatchController *watchController = [self valueForKey:@"_UIDelegate"];
[watchController showFullScreen];
}
%new
- (void)shortsToRegular {
if (self.contentVideoID != nil && [self.parentViewController isKindOfClass:NSClassFromString(@"YTShortsPlayerViewController")]) {
NSString *vidLink = [NSString stringWithFormat:@"vnd.youtube://%@", self.contentVideoID];
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:vidLink]]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:vidLink] options:@{} completionHandler:nil];
}
}
}
%new
- (void)turnOffCaptions {
[self setActiveCaptionTrack:nil];
}
%new
- (void)autoQuality {
if (![self.view.superview isKindOfClass:NSClassFromString(@"YTWatchView")]) {
return;
}
NetworkStatus status = [[Reachability reachabilityForInternetConnection] currentReachabilityStatus];
NSInteger kQualityIndex = status == ReachableViaWiFi ? kWiFiQualityIndex : kCellQualityIndex;
NSString *bestQualityLabel;
int highestResolution = 0;
for (MLFormat *format in self.activeVideo.selectableVideoFormats) {
int reso = format.singleDimensionResolution;
if (reso > highestResolution) {
highestResolution = reso;
bestQualityLabel = format.qualityLabel;
}
}
NSString *qualityLabel = kQualityIndex == 1 ? bestQualityLabel :
kQualityIndex == 2 ? @"2160p60" :
kQualityIndex == 3 ? @"2160p" :
kQualityIndex == 4 ? @"1440p60" :
kQualityIndex == 5 ? @"1440p" :
kQualityIndex == 6 ? @"1080p60" :
kQualityIndex == 7 ? @"1080p" :
kQualityIndex == 8 ? @"720p60" :
kQualityIndex == 9 ? @"720p" :
kQualityIndex == 10 ? @"480p" :
kQualityIndex == 11 ? @"360p" :
nil;
if (![qualityLabel isEqualToString:bestQualityLabel]) {
BOOL exactMatch = NO;
NSString *closestQualityLabel = qualityLabel;
for (MLFormat *format in self.activeVideo.selectableVideoFormats) {
if ([format.qualityLabel isEqualToString:qualityLabel]) {
exactMatch = YES;
break;
}
}
if (!exactMatch) {
NSInteger bestQualityDifference = NSIntegerMax;
for (MLFormat *format in self.activeVideo.selectableVideoFormats) {
NSArray *formatСomponents = [format.qualityLabel componentsSeparatedByString:@"p"];
NSArray *targetComponents = [qualityLabel componentsSeparatedByString:@"p"];
if (formatСomponents.count == 2) {
NSInteger formatQuality = [formatСomponents.firstObject integerValue];
NSInteger targetQuality = [targetComponents.firstObject integerValue];
NSInteger difference = labs(formatQuality - targetQuality);
if (difference < bestQualityDifference) {
bestQualityDifference = difference;
closestQualityLabel = format.qualityLabel;
}
}
}
qualityLabel = closestQualityLabel;
}
}
MLQuickMenuVideoQualitySettingFormatConstraint *fc = [[%c(MLQuickMenuVideoQualitySettingFormatConstraint) alloc] init];
if ([fc respondsToSelector:@selector(initWithVideoQualitySetting:formatSelectionReason:qualityLabel:)]) {
[self.activeVideo setVideoFormatConstraint:[fc initWithVideoQualitySetting:3 formatSelectionReason:2 qualityLabel:qualityLabel]];
}
}
%end
// Exit Fullscreen on Finish
%hook YTWatchFlowController
- (BOOL)shouldExitFullScreenOnFinish { return kExitFullscreen ? YES : NO; }
%end
%hook YTMainAppVideoPlayerOverlayViewController
// Disable Double Tap To Seek
- (BOOL)allowDoubleTapToSeekGestureRecognizer { return kNoDoubleTapToSeek ? NO : %orig; }
// Copy Timestamped Link by Pressing On Pause
- (void)didPressPause:(id)arg1 {
%orig;
if (kCopyWithTimestamp) {
NSInteger mediaTimeInteger = (NSInteger)self.mediaTime;
NSString *currentTimeLink = [NSString stringWithFormat:@"https://www.youtube.com/watch?v=%@&t=%lds", self.videoID, mediaTimeInteger];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = currentTimeLink;
}
}
%end
// Fit 'Play All' Buttons Text For Localizations
%hook YTQTMButton
- (void)layoutSubviews {
if ([self.accessibilityIdentifier isEqualToString:@"id.playlist.playall.button"]) {
self.titleLabel.adjustsFontSizeToFitWidth = YES;
} %orig;
}
%end
// Fit Shorts Button Labels For Localizations
%hook YTReelPlayerButton
- (void)layoutSubviews {
%orig;
for (UIView *subview in self.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)subview;
label.adjustsFontSizeToFitWidth = YES;
break;
}
}
}
%end
// Fix Playlist Mini-bar Height For Small Screens
%hook YTPlaylistMiniBarView
- (void)setFrame:(CGRect)frame {
if (frame.size.height < 54.0) frame.size.height = 54.0;
%orig(frame);
}
%end
// Remove "Play next in queue" from the menu @PoomSmart (https://github.com/qnblackcat/uYouPlus/issues/1138#issuecomment-1606415080)
%hook YTMenuItemVisibilityHandler
- (BOOL)shouldShowServiceItemRenderer:(YTIMenuConditionalServiceItemRenderer *)renderer {
if (kRemovePlayNext && renderer.icon.iconType == 251) {
return NO;
} return %orig;
}
%end
// Remove Download button from the menu
%hook YTDefaultSheetController
- (void)addAction:(YTActionSheetAction *)action {
NSString *identifier = [action valueForKey:@"_accessibilityIdentifier"];
NSDictionary *actionsToRemove = @{
@"7": @(kRemoveDownloadMenu),
@"1": @(kRemoveWatchLaterMenu),
@"3": @(kRemoveSaveToPlaylistMenu),
@"5": @(kRemoveShareMenu),
@"12": @(kRemoveNotInterestedMenu),
@"31": @(kRemoveDontRecommendMenu),
@"58": @(kRemoveReportMenu)
};
if (![actionsToRemove[identifier] boolValue]) {
%orig;
}
}
%end
// Hide buttons under the video player (@PoomSmart)
static BOOL findCell(ASNodeController *nodeController, NSArray <NSString *> *identifiers) {
for (id child in [nodeController children]) {
if ([child isKindOfClass:%c(ELMNodeController)]) {
NSArray <ELMComponent *> *elmChildren = [(ELMNodeController *)child children];
for (ELMComponent *elmChild in elmChildren) {
for (NSString *identifier in identifiers) {
if ([[elmChild description] containsString:identifier])
return YES;
}
}
}
if ([child isKindOfClass:%c(ASNodeController)]) {
ASDisplayNode *childNode = ((ASNodeController *)child).node; // ELMContainerNode
NSArray *yogaChildren = childNode.yogaChildren;
for (ASDisplayNode *displayNode in yogaChildren) {
if ([identifiers containsObject:displayNode.accessibilityIdentifier])
return YES;
}
return findCell(child, identifiers);
}
return NO;
}
return NO;
}
%hook ASCollectionView
- (CGSize)sizeForElement:(ASCollectionElement *)element {
if ([self.accessibilityIdentifier isEqualToString:@"id.video.scrollable_action_bar"]) {
ASCellNode *node = [element node];
ASNodeController *nodeController = [node controller];
if (kNoPlayerRemixButton && findCell(nodeController, @[@"id.video.remix.button"])) {
return CGSizeZero;
}
if (kNoPlayerClipButton && findCell(nodeController, @[@"clip_button.eml"])) {
return CGSizeZero;
}
if (kNoPlayerDownloadButton && findCell(nodeController, @[@"id.ui.add_to.offline.button"])) {
return CGSizeZero;
}
}
return %orig;
}
%end
// Remove Premium Pop-up, Horizontal Video Carousel and Shorts (https://github.com/MiRO92/YTNoShorts)
%hook YTAsyncCollectionView
- (id)cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = %orig;
if ([cell isKindOfClass:objc_lookUpClass("_ASCollectionViewCell")]) {
_ASCollectionViewCell *cell = %orig;
if ([cell respondsToSelector:@selector(node)]) {
NSString *idToRemove = [[cell node] accessibilityIdentifier];
if ([idToRemove isEqualToString:@"statement_banner.view"] ||
(([idToRemove isEqualToString:@"eml.shorts-grid"] || [idToRemove isEqualToString:@"eml.shorts-shelf"]) && kHideShorts)) {
[self removeCellsAtIndexPath:indexPath];
}
}
} else if (([cell isKindOfClass:objc_lookUpClass("YTReelShelfCell")] && kHideShorts) ||
([cell isKindOfClass:objc_lookUpClass("YTHorizontalCardListCell")] && kNoContinueWatching)) {
[self removeCellsAtIndexPath:indexPath];
} return %orig;
}
%new
- (void)removeCellsAtIndexPath:(NSIndexPath *)indexPath {
[self deleteItemsAtIndexPaths:@[indexPath]];
}
%end
// Shorts Progress Bar (https://github.com/PoomSmart/YTShortsProgress)
%hook YTReelPlayerViewController
- (BOOL)shouldEnablePlayerBar { return kShortsProgress ? YES : NO; }
- (BOOL)shouldAlwaysEnablePlayerBar { return kShortsProgress ? YES : NO; }
- (BOOL)shouldEnablePlayerBarOnlyOnPause { return kShortsProgress ? NO : YES; }
%end
%hook YTReelPlayerViewControllerSub
- (BOOL)shouldEnablePlayerBar { return kShortsProgress ? YES : NO; }
- (BOOL)shouldAlwaysEnablePlayerBar { return kShortsProgress ? YES : NO; }
- (BOOL)shouldEnablePlayerBarOnlyOnPause { return kShortsProgress ? NO : YES; }
%end
%hook YTShortsPlayerViewController
- (BOOL)shouldAlwaysEnablePlayerBar { return kShortsProgress ? YES : NO; }
- (BOOL)shouldEnablePlayerBarOnlyOnPause { return kShortsProgress ? NO : YES; }
%end
%hook YTColdConfig
- (BOOL)iosEnableVideoPlayerScrubber { return kShortsProgress ? YES : NO; }
- (BOOL)mobileShortsTabInlined { return kShortsProgress ? YES : NO; }
%end
%hook YTHotConfig
- (BOOL)enablePlayerBarForVerticalVideoWhenControlsHiddenInFullscreen { return kShortsProgress ? YES : NO; }
%end
// Dont Startup Shorts
%hook YTShortsStartupCoordinator
- (id)evaluateResumeToShorts { return kResumeShorts ? nil : %orig; }
%end
// Hide Shorts Elements
%hook YTReelPausedStateCarouselView
- (void)setPausedStateCarouselVisible:(BOOL)arg1 animated:(BOOL)arg2 { kHideShortsSubscriptions ? %orig(arg1 = NO, arg2) : %orig; }
%end
%hook YTReelWatchPlaybackOverlayView
- (void)setReelLikeButton:(id)arg1 { if (!kHideShortsLike) %orig; }
- (void)setReelDislikeButton:(id)arg1 { if (!kHideShortsDislike) %orig; }
- (void)setViewCommentButton:(id)arg1 { if (!kHideShortsComments) %orig; }
- (void)setRemixButton:(id)arg1 { if (!kHideShortsRemix) %orig; }
- (void)setShareButton:(id)arg1 { if (!kHideShortsShare) %orig; }
- (void)setNativePivotButton:(id)arg1 { if (!kHideShortsAvatars) %orig; }
- (void)setPivotButtonElementRenderer:(id)arg1 { if (!kHideShortsAvatars) %orig; }
%end
%hook YTReelHeaderView
- (void)setTitleLabelVisible:(BOOL)arg1 animated:(BOOL)arg2 { kHideShortsLogo ? %orig(arg1 = NO, arg2) : %orig; }
%end
%hook YTReelTransparentStackView
- (void)layoutSubviews {
%orig;
for (YTQTMButton *button in self.subviews) {
if ([button respondsToSelector:@selector(buttonRenderer)]) {
if (kHideShortsSearch && button.buttonRenderer.icon.iconType == 1045) button.hidden = YES;
if (kHideShortsCamera && button.buttonRenderer.icon.iconType == 1046) button.hidden = YES;
if (kHideShortsMore && button.buttonRenderer.icon.iconType == 1047) button.hidden = YES;
}
}
}
%end
%hook YTReelWatchHeaderView
- (void)setChannelBarElementRenderer:(id)renderer { if (!kHideShortsChannelName) %orig; }
- (void)setHeaderRenderer:(id)renderer { if (!kHideShortsDescription) %orig; }
- (void)setSoundMetadataElementRenderer:(id)renderer { if (!kHideShortsAudioTrack) %orig; }
- (void)setActionElement:(id)renderer { if (!kHideShortsPromoCards) %orig; }
- (void)setBadgeRenderer:(id)renderer { if (!kHideShortsThanks) %orig; }
- (void)setMultiFormatLinkElementRenderer:(id)renderer { if (!kHideShortsSource) %orig; }
%end
static BOOL isOverlayShown = YES;
%hook YTPlayerView
- (void)didPinch:(UIPinchGestureRecognizer *)gesture {
%orig;
if (kPinchToFullscreenShorts && [self.playerViewDelegate.parentViewController isKindOfClass:NSClassFromString(@"YTShortsPlayerViewController")]) {
YTShortsPlayerViewController *shortsPlayerVC = (YTShortsPlayerViewController *)self.playerViewDelegate.parentViewController;
YTReelContentView *contentView = (YTReelContentView *)shortsPlayerVC.view;
if (gesture.scale > 1) {
if (!kShortsOnlyMode) [shortsPlayerVC.navigationController.parentViewController hidePivotBar];
[UIView animateWithDuration:0.3 animations:^{
contentView.playbackOverlay.alpha = 0;
isOverlayShown = contentView.playbackOverlay.alpha;
}];
} else {
if (!kShortsOnlyMode) [shortsPlayerVC.navigationController.parentViewController showPivotBar];
[UIView animateWithDuration:0.3 animations:^{
contentView.playbackOverlay.alpha = 1;
isOverlayShown = contentView.playbackOverlay.alpha;
}];
}
}
}
- (void)layoutSubviews {
%orig;
if (kShortsOnlyMode && [self.playerViewDelegate.parentViewController isKindOfClass:NSClassFromString(@"YTShortsPlayerViewController")]) {
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(turnShortsOnlyModeOff:)];
longPressGesture.numberOfTouchesRequired = 2;
longPressGesture.minimumPressDuration = 0.5;
[self addGestureRecognizer:longPressGesture];
}
}
%new
- (void)turnShortsOnlyModeOff:(UILongPressGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"YTLite.plist"];
NSMutableDictionary *prefs = [NSMutableDictionary dictionaryWithContentsOfFile:path];
[prefs setObject:@NO forKey:@"shortsOnlyMode"];
[prefs writeToFile:path atomically:NO];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.dvntm.ytlite.prefschanged"), NULL, NULL, YES);
UIResponder *responder = self.nextResponder;
while (responder && ![responder isKindOfClass:[UIViewController class]]) responder = responder.nextResponder;
if (responder) [[%c(YTToastResponderEvent) eventWithMessage:LOC(@"ShortsModeTurnedOff") firstResponder:responder] send];
YTShortsPlayerViewController *shortsPlayerVC = (YTShortsPlayerViewController *)self.playerViewDelegate.parentViewController;
[shortsPlayerVC.navigationController.parentViewController performSelector:@selector(showPivotBar) withObject:nil afterDelay:1.0];
}
}
%end
%hook YTReelWatchPlaybackOverlayView
- (void)layoutSubviews {
%orig;
self.alpha = isOverlayShown;
}
%end
static void downloadImageFromURL(UIResponder *responder, NSURL *URL, BOOL download) {
NSString *URLString = URL.absoluteString;
if (kFixAlbums && [URLString hasPrefix:@"https://yt3."]) {
URLString = [URLString stringByReplacingOccurrencesOfString:@"https://yt3." withString:@"https://yt4."];
}
NSURL *downloadURL = nil;
if ([URLString containsString:@"c-fcrop"]) {
NSRange croppedURL = [URLString rangeOfString:@"c-fcrop"];
if (croppedURL.location != NSNotFound) {
NSString *newURL = [URLString stringByReplacingOccurrencesOfString:[URLString substringFromIndex:croppedURL.location] withString:@"nd-v1"];
downloadURL = [NSURL URLWithString:newURL];
}
} else {
downloadURL = URL;
}
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:downloadURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
if (download) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAsset];
[request addResourceWithType:PHAssetResourceTypePhoto data:data options:nil];
} completionHandler:^(BOOL success, NSError *error) {
[[%c(YTToastResponderEvent) eventWithMessage:success ? LOC(@"Saved") : [NSString stringWithFormat:LOC(@"%@: %@"), LOC(@"Error"), error.localizedDescription] firstResponder:responder] send];
}];
} else {
[UIPasteboard generalPasteboard].image = [UIImage imageWithData:data];
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:responder] send];
}
} else {
[[%c(YTToastResponderEvent) eventWithMessage:[NSString stringWithFormat:LOC(@"%@: %@"), LOC(@"Error"), error.localizedDescription] firstResponder:responder] send];
}
}] resume];
}
static void genImageFromLayer(CALayer *layer, UIColor *backgroundColor, void (^completionHandler)(UIImage *)) {
UIGraphicsBeginImageContextWithOptions(layer.frame.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, layer.frame.size.width, layer.frame.size.height));
[layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (completionHandler) {
completionHandler(image);
}
}
%hook ELMContainerNode
%property (nonatomic, strong) NSString *copiedComment;
%property (nonatomic, strong) NSURL *copiedURL;
%end
%hook ASDisplayNode
- (void)setFrame:(CGRect)frame {
%orig;
if (kCommentManager && [[self valueForKey:@"_accessibilityIdentifier"] isEqualToString:@"id.comment.content.label"]) {
ASTextNode *textNode = (ASTextNode *)self;
NSString *comment = textNode.attributedText.string;
NSMutableArray *allObjects = self.supernodes.allObjects;
for (ELMContainerNode *containerNode in allObjects) {
if ([containerNode.description containsString:@"id.ui.comment_cell"] && comment) {
containerNode.copiedComment = comment;
break;
}
}
}
if (kPostManager && [self isKindOfClass:NSClassFromString(@"ELMExpandableTextNode")]) {
ELMExpandableTextNode *expandableTextNode = (ELMExpandableTextNode *)self;
ASTextNode *textNode = (ASTextNode *)expandableTextNode.currentTextNode;
NSString *text = textNode.attributedText.string;
NSMutableArray *allObjects = self.supernodes.allObjects;
for (ELMContainerNode *containerNode in allObjects) {
if ([containerNode.description containsString:@"id.ui.backstage.original_post"] && text) {
containerNode.copiedComment = text;
break;
}
}
}
}
%end
%hook YTImageZoomNode
- (BOOL)gestureRecognizer:(id)arg1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(id)arg2 {
BOOL isImageLoaded = [self valueForKey:@"_didLoadImage"];
if (kPostManager && isImageLoaded) {
ASDisplayNode *displayNode = (ASDisplayNode *)self;
ASNetworkImageNode *imageNode = (ASNetworkImageNode *)self;
NSURL *URL = imageNode.URL;
NSMutableArray *allObjects = displayNode.supernodes.allObjects;
for (ELMContainerNode *containerNode in allObjects) {
if ([containerNode.description containsString:@"id.ui.backstage.original_post"]) {
containerNode.copiedURL = URL;
break;
}
}
}
return %orig;
}
%end
%hook _ASDisplayView
- (void)setKeepalive_node:(id)arg1 {
%orig;
NSArray *gesturesInfo = @[
@{@"selector": @"postManager:", @"text": @"id.ui.backstage.original_post", @"key": @(kPostManager)},
@{@"selector": @"savePFP:", @"text": @"ELMImageNode-View", @"key": @(kSaveProfilePhoto)},
@{@"selector": @"commentManager:", @"text": @"id.ui.comment_cell", @"key": @(kCommentManager)}
];
for (NSDictionary *gestureInfo in gesturesInfo) {
SEL selector = NSSelectorFromString(gestureInfo[@"selector"]);
if ([gestureInfo[@"key"] boolValue] && [[self description] containsString:gestureInfo[@"text"]]) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:selector];
longPress.minimumPressDuration = 0.3;
[self addGestureRecognizer:longPress];
break;
}
}
}
%new
- (void)savePFP:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
ASNetworkImageNode *imageNode = (ASNetworkImageNode *)self.keepalive_node;
NSString *URLString = imageNode.URL.absoluteString;
if (URLString) {
NSRange sizeRange = [URLString rangeOfString:@"=s"];
if (sizeRange.location != NSNotFound) {
NSRange dashRange = [URLString rangeOfString:@"-" options:0 range:NSMakeRange(sizeRange.location, URLString.length - sizeRange.location)];
if (dashRange.location != NSNotFound) {
NSString *newURLString = [URLString stringByReplacingCharactersInRange:NSMakeRange(sizeRange.location + 2, dashRange.location - sizeRange.location - 2) withString:@"1024"];
NSURL *PFPURL = [NSURL URLWithString:newURLString];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:PFPURL]];
if (image) {
YTDefaultSheetController *sheetController = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:nil];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"SaveProfilePicture") iconImage:YTImageNamed(@"yt_outline_image_24pt") style:0 handler:^ {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Saved") firstResponder:self.keepalive_node.closestViewController] send];
}]];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyProfilePicture") iconImage:YTImageNamed(@"yt_outline_library_image_24pt") style:0 handler:^ {
[UIPasteboard generalPasteboard].image = image;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:self.keepalive_node.closestViewController] send];
}]];
[sheetController presentFromViewController:self.keepalive_node.closestViewController animated:YES completion:nil];
}
}
}
}
}
}
%new
- (void)postManager:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
ELMContainerNode *nodeForLayer = (ELMContainerNode *)self.keepalive_node.yogaChildren[0];
ELMContainerNode *containerNode = (ELMContainerNode *)self.keepalive_node;
NSString *text = containerNode.copiedComment;
NSURL *URL = containerNode.copiedURL;
CALayer *layer = nodeForLayer.layer;
UIColor *backgroundColor = containerNode.closestViewController.view.backgroundColor;
YTDefaultSheetController *sheetController = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:nil];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyPostText") iconImage:YTImageNamed(@"yt_outline_message_bubble_right_24pt") style:0 handler:^ {
if (text) {
[UIPasteboard generalPasteboard].string = text;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:containerNode.closestViewController] send];
}
}]];
if (URL) {
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"SaveCurrentImage") iconImage:YTImageNamed(@"yt_outline_image_24pt") style:0 handler:^ {
downloadImageFromURL(containerNode.closestViewController, URL, YES);
}]];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyCurrentImage") iconImage:YTImageNamed(@"yt_outline_library_image_24pt") style:0 handler:^ {
downloadImageFromURL(containerNode.closestViewController, URL, NO);
}]];
}
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"SavePostAsImage") titleColor:[[UIColor redColor] colorWithAlphaComponent:0.7f] iconImage:YTImageNamed(@"yt_outline_image_24pt") iconColor:[[UIColor redColor] colorWithAlphaComponent:0.7f] disableAutomaticButtonColor:YES accessibilityIdentifier:nil handler:^ {
genImageFromLayer(layer, backgroundColor, ^(UIImage *image) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
request.creationDate = [NSDate date];
} completionHandler:^(BOOL success, NSError *error) {
NSString *message = success ? LOC(@"Saved") : [NSString stringWithFormat:LOC(@"%@: %@"), LOC(@"Error"), error.localizedDescription];
[[%c(YTToastResponderEvent) eventWithMessage:message firstResponder:containerNode.closestViewController] send];
}];
});
}]];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyPostAsImage") titleColor:[[UIColor redColor] colorWithAlphaComponent:0.7f] iconImage:YTImageNamed(@"yt_outline_library_image_24pt") iconColor:[[UIColor redColor] colorWithAlphaComponent:0.7f] disableAutomaticButtonColor:YES accessibilityIdentifier:nil handler:^ {
genImageFromLayer(layer, backgroundColor, ^(UIImage *image) {
[UIPasteboard generalPasteboard].image = image;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:containerNode.closestViewController] send];
});
}]];
[sheetController presentFromViewController:containerNode.closestViewController animated:YES completion:nil];
}
}
%new
- (void)commentManager:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
ELMContainerNode *containerNode = (ELMContainerNode *)self.keepalive_node;
NSString *comment = containerNode.copiedComment;
CALayer *layer = self.layer;
UIColor *backgroundColor = containerNode.closestViewController.view.backgroundColor;
YTDefaultSheetController *sheetController = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:nil];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyCommentText") iconImage:YTImageNamed(@"yt_outline_message_bubble_right_24pt") style:0 handler:^ {
if (comment) {
[UIPasteboard generalPasteboard].string = comment;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:containerNode.closestViewController] send];
}
}]];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"SaveCommentAsImage") iconImage:YTImageNamed(@"yt_outline_image_24pt") style:0 handler:^ {
genImageFromLayer(layer, backgroundColor, ^(UIImage *image) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
request.creationDate = [NSDate date];
} completionHandler:^(BOOL success, NSError *error) {
NSString *message = success ? LOC(@"Saved") : [NSString stringWithFormat:LOC(@"%@: %@"), LOC(@"Error"), error.localizedDescription];
[[%c(YTToastResponderEvent) eventWithMessage:message firstResponder:containerNode.closestViewController] send];
}];
});
}]];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyCommentAsImage") iconImage:YTImageNamed(@"yt_outline_library_image_24pt") style:0 handler:^ {
genImageFromLayer(layer, backgroundColor, ^(UIImage *image) {
[UIPasteboard generalPasteboard].image = image;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:containerNode.closestViewController] send];
});
}]];
[sheetController presentFromViewController:containerNode.closestViewController animated:YES completion:nil];
}
}
%end
// Remove Tabs
%hook YTPivotBarView
- (void)setRenderer:(YTIPivotBarRenderer *)renderer {
NSMutableArray <YTIPivotBarSupportedRenderers *> *items = [renderer itemsArray];
NSDictionary *identifiersToRemove = @{
@"FEshorts": @[@(kRemoveShorts), @(kReExplore)],
@"FEsubscriptions": @[@(kRemoveSubscriptions)],
@"FEuploads": @[@(kRemoveUploads)],
@"FElibrary": @[@(kRemoveLibrary)]
};
for (NSString *identifier in identifiersToRemove) {
NSArray *removeValues = identifiersToRemove[identifier];
BOOL shouldRemoveItem = [removeValues containsObject:@(YES)];
NSUInteger index = [items indexOfObjectPassingTest:^BOOL(YTIPivotBarSupportedRenderers *renderer, NSUInteger idx, BOOL *stop) {
if ([identifier isEqualToString:@"FEuploads"]) {
return shouldRemoveItem && [[[renderer pivotBarIconOnlyItemRenderer] pivotIdentifier] isEqualToString:identifier];
} else {
return shouldRemoveItem && [[[renderer pivotBarItemRenderer] pivotIdentifier] isEqualToString:identifier];
}
}];
if (index != NSNotFound) {
[items removeObjectAtIndex:index];
}
}
NSUInteger exploreIndex = [items indexOfObjectPassingTest:^BOOL(YTIPivotBarSupportedRenderers *renderers, NSUInteger idx, BOOL *stop) {
return [[[renderers pivotBarItemRenderer] pivotIdentifier] isEqualToString:[%c(YTIBrowseRequest) browseIDForExploreTab]];
}];
if (exploreIndex == NSNotFound && (kReExplore || kAddExplore)) {
YTIPivotBarSupportedRenderers *exploreTab = [%c(YTIPivotBarRenderer) pivotSupportedRenderersWithBrowseId:[%c(YTIBrowseRequest) browseIDForExploreTab] title:LOC(@"Explore") iconType:292];
[items insertObject:exploreTab atIndex:1];
}
%orig;
}
%end
// Hide Tab Bar Indicators
%hook YTPivotBarIndicatorView
- (void)setFillColor:(id)arg1 { %orig(kRemoveIndicators ? [UIColor clearColor] : arg1); }
- (void)setBorderColor:(id)arg1 { %orig(kRemoveIndicators ? [UIColor clearColor] : arg1); }
%end
// Hide Tab Labels
%hook YTPivotBarItemView
- (void)setRenderer:(YTIPivotBarRenderer *)renderer {
%orig;
if (kRemoveLabels) {
[self.navigationButton setTitle:@"" forState:UIControlStateNormal];
[self.navigationButton setSizeWithPaddingAndInsets:NO];
}
}
%end
// Startup Tab
BOOL isTabSelected = NO;
%hook YTPivotBarViewController
- (void)viewDidAppear:(BOOL)animated {
%orig;
if (!isTabSelected && !kShortsOnlyMode) {
NSString *pivotIdentifier = kPivotIndex == 1 ? @"FEexplore" :
kPivotIndex == 2 ? @"FEshorts" :
kPivotIndex == 3 ? @"FEsubscriptions" :
kPivotIndex == 4 ? @"FElibrary" :
@"FEwhat_to_watch";
[self selectItemWithPivotIdentifier:pivotIdentifier];
isTabSelected = YES;
}
if (kShortsOnlyMode) {
[self selectItemWithPivotIdentifier:@"FEshorts"];
[self.parentViewController hidePivotBar];
}
}
%end
%hook YTTabsViewController
- (void)viewDidAppear:(BOOL)animated {
%orig;
isOverlayShown = YES;
if (kShortsOnlyMode) {
[self.navigationController.parentViewController hidePivotBar];
}
}
%end
%hook YTAppViewController
- (void)showPivotBar { if (!kShortsOnlyMode) %orig; }
%end
%hook YTReelWatchRootViewController
- (void)viewDidAppear:(BOOL)animated {
%orig;
if (kShortsOnlyMode) {
[self.navigationController.parentViewController hidePivotBar];
}
}
%end
%hook YTEngagementPanelView
- (void)layoutSubviews {
%orig;
if (kCopyVideoInfo && [self.panelIdentifier.identifierString isEqualToString:@"video-description-ep-identifier"]) {
YTQTMButton *copyInfoButton = [%c(YTQTMButton) iconButton];
copyInfoButton.accessibilityLabel = LOC(@"CopyVideoInfo");
[copyInfoButton setTag:999];
[copyInfoButton enableNewTouchFeedback];
[copyInfoButton setImage:YTImageNamed(@"yt_outline_copy_24pt") forState:UIControlStateNormal];
[copyInfoButton setTintColor:[UIColor labelColor]];
[copyInfoButton setTranslatesAutoresizingMaskIntoConstraints:false];
[copyInfoButton addTarget:self action:@selector(didTapCopyInfoButton:) forControlEvents:UIControlEventTouchUpInside];
if (self.headerView && ![self.headerView viewWithTag:999]) {
[self.headerView addSubview:copyInfoButton];
[NSLayoutConstraint activateConstraints:@[
[copyInfoButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-48],
[copyInfoButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[copyInfoButton.widthAnchor constraintEqualToConstant:40.0],
[copyInfoButton.heightAnchor constraintEqualToConstant:40.0],
]];
}
}
}
%new
- (void)didTapCopyInfoButton:(UIButton *)sender {
YTPlayerViewController *playerVC = self.resizeDelegate.parentViewController.parentViewController.parentViewController.playerViewController;
NSString *title = playerVC.playerResponse.playerData.videoDetails.title;
NSString *shortDescription = playerVC.playerResponse.playerData.videoDetails.shortDescription;
YTDefaultSheetController *sheetController = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:nil];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyTitle") iconImage:YTImageNamed(@"yt_outline_text_box_24pt") style:0 handler:^ {
[UIPasteboard generalPasteboard].string = title;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:self.resizeDelegate] send];
}]];
[sheetController addAction:[%c(YTActionSheetAction) actionWithTitle:LOC(@"CopyDescription") iconImage:YTImageNamed(@"yt_outline_message_bubble_right_24pt") style:0 handler:^ {
[UIPasteboard generalPasteboard].string = shortDescription;
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"Copied") firstResponder:self.resizeDelegate] send];
}]];
[sheetController presentFromViewController:self.resizeDelegate animated:YES completion:nil];
}
%end
// Disable Right-To-Left Formatting
%hook NSParagraphStyle
+ (NSWritingDirection)defaultWritingDirectionForLanguage:(id)lang { return kDisableRTL ? NSWritingDirectionLeftToRight : %orig; }
+ (NSWritingDirection)_defaultWritingDirection { return kDisableRTL ? NSWritingDirectionLeftToRight : %orig; }
%end
// Fix Albums For Russian Users
static NSURL *newCoverURL(NSURL *originalURL) {
NSDictionary <NSString *, NSString *> *hostsToReplace = @{
@"yt3.ggpht.com": @"yt4.ggpht.com",
@"yt3.googleusercontent.com": @"yt4.googleusercontent.com",
};
NSString *const replacement = hostsToReplace[originalURL.host];
if (kFixAlbums && replacement) {
NSURLComponents *components = [NSURLComponents componentsWithURL:originalURL resolvingAgainstBaseURL:NO];
components.host = replacement;
return components.URL;
}
return originalURL;
}
%hook ELMImageDownloader
- (id)downloadImageWithURL:(id)arg1 targetSize:(CGSize)arg2 callbackQueue:(id)arg3 downloadProgress:(id)arg4 completion:(id)arg5 {
return %orig(newCoverURL(arg1), arg2, arg3, arg4, arg5);
}
%end
// Not necessary but preferred
%hook ASBasicImageDownloader
- (id)downloadImageWithURL:(id)arg1 shouldRetry:(BOOL)arg2 callbackQueue:(id)arg3 downloadProgress:(id)arg4 completion:(id)arg5 {
return %orig(newCoverURL(arg1), arg2, arg3, arg4, arg5);
}
%end
static void reloadPrefs() {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"YTLite.plist"];
NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:path];
kNoAds = (prefs[@"noAds"] != nil) ? [prefs[@"noAds"] boolValue] : YES;
kBackgroundPlayback = (prefs[@"backgroundPlayback"] != nil) ? [prefs[@"backgroundPlayback"] boolValue] : YES;
kNoCast = [prefs[@"noCast"] boolValue] ?: NO;
kNoNotifsButton = [prefs[@"removeNotifsButton"] boolValue] ?: NO;
kNoSearchButton = [prefs[@"removeSearchButton"] boolValue] ?: NO;
kNoVoiceSearchButton = [prefs[@"removeVoiceSearchButton"] boolValue] ?: NO;
kStickyNavbar = [prefs[@"stickyNavbar"] boolValue] ?: NO;
kNoSubbar = [prefs[@"noSubbar"] boolValue] ?: NO;
kNoYTLogo = [prefs[@"noYTLogo"] boolValue] ?: NO;
kHideAutoplay = [prefs[@"hideAutoplay"] boolValue] ?: NO;
kHideSubs = [prefs[@"hideSubs"] boolValue] ?: NO;
kNoHUDMsgs = [prefs[@"noHUDMsgs"] boolValue] ?: NO;
kHidePrevNext = [prefs[@"hidePrevNext"] boolValue] ?: NO;
kReplacePrevNext = [prefs[@"replacePrevNext"] boolValue] ?: NO;
kNoDarkBg = [prefs[@"noDarkBg"] boolValue] ?: NO;
kEndScreenCards = [prefs[@"endScreenCards"] boolValue] ?: NO;
kNoFullscreenActions = [prefs[@"noFullscreenActions"] boolValue] ?: NO;
kPersistentProgressBar = [prefs[@"persistentProgressBar"] boolValue] ?: NO;
kNoRelatedVids = [prefs[@"noRelatedVids"] boolValue] ?: NO;
kNoPromotionCards = [prefs[@"noPromotionCards"] boolValue] ?: NO;
kNoWatermarks = [prefs[@"noWatermarks"] boolValue] ?: NO;
kMiniplayer = [prefs[@"miniplayer"] boolValue] ?: NO;
kPortraitFullscreen = [prefs[@"portraitFullscreen"] boolValue] ?: NO;
kCopyWithTimestamp = [prefs[@"copyWithTimestamp"] boolValue] ?: NO;
kDisableAutoplay = [prefs[@"disableAutoplay"] boolValue] ?: NO;
kDisableAutoCaptions = [prefs[@"disableAutoCaptions"] boolValue] ?: NO;
kNoContentWarning = [prefs[@"noContentWarning"] boolValue] ?: NO;
kClassicQuality = [prefs[@"classicQuality"] boolValue] ?: NO;
kExtraSpeedOptions = [prefs[@"extraSpeedOptions"] boolValue] ?: NO;
kDontSnapToChapter = [prefs[@"dontSnapToChapter"] boolValue] ?: NO;
kRedProgressBar = [prefs[@"redProgressBar"] boolValue] ?: NO;
kNoPlayerRemixButton = [prefs[@"noPlayerRemixButton"] boolValue] ?: NO;
kNoPlayerClipButton = [prefs[@"noPlayerClipButton"] boolValue] ?: NO;
kNoPlayerDownloadButton = [prefs[@"noPlayerDownloadButton"] boolValue] ?: NO;
kNoHints = [prefs[@"noHints"] boolValue] ?: NO;
kNoFreeZoom = [prefs[@"noFreeZoom"] boolValue] ?: NO;
kAutoFullscreen = [prefs[@"autoFullscreen"] boolValue] ?: NO;
kExitFullscreen = [prefs[@"exitFullscreen"] boolValue] ?: NO;
kNoDoubleTapToSeek = [prefs[@"noDoubleTapToSeek"] boolValue] ?: NO;
kShortsOnlyMode = [prefs[@"shortsOnlyMode"] boolValue] ?: NO;
kHideShorts = [prefs[@"hideShorts"] boolValue] ?: NO;
kShortsProgress = [prefs[@"shortsProgress"] boolValue] ?: NO;
kPinchToFullscreenShorts = [prefs[@"pinchToFullscreenShorts"] boolValue] ?: NO;
kShortsToRegular = [prefs[@"shortsToRegular"] boolValue] ?: NO;
kResumeShorts = [prefs[@"resumeShorts"] boolValue] ?: NO;
kHideShortsLogo = [prefs[@"hideShortsLogo"] boolValue] ?: NO;
kHideShortsSearch = [prefs[@"hideShortsSearch"] boolValue] ?: NO;
kHideShortsCamera = [prefs[@"hideShortsCamera"] boolValue] ?: NO;
kHideShortsMore = [prefs[@"hideShortsMore"] boolValue] ?: NO;
kHideShortsSubscriptions = [prefs[@"hideShortsSubscriptions"] boolValue] ?: NO;
kHideShortsLike = [prefs[@"hideShortsLike"] boolValue] ?: NO;
kHideShortsDislike = [prefs[@"hideShortsDislike"] boolValue] ?: NO;
kHideShortsComments = [prefs[@"hideShortsComments"] boolValue] ?: NO;
kHideShortsRemix = [prefs[@"hideShortsRemix"] boolValue] ?: NO;
kHideShortsShare = [prefs[@"hideShortsShare"] boolValue] ?: NO;
kHideShortsAvatars = [prefs[@"hideShortsAvatars"] boolValue] ?: NO;
kHideShortsThanks = [prefs[@"hideShortsThanks"] boolValue] ?: NO;
kHideShortsSource = [prefs[@"hideShortsSource"] boolValue] ?: NO;
kHideShortsChannelName = [prefs[@"hideShortsChannelName"] boolValue] ?: NO;
kHideShortsDescription = [prefs[@"hideShortsDescription"] boolValue] ?: NO;
kHideShortsAudioTrack = [prefs[@"hideShortsAudioTrack"] boolValue] ?: NO;
kHideShortsPromoCards = [prefs[@"hideShortsPromoCards"] boolValue] ?: NO;
kRemoveLabels = [prefs[@"removeLabels"] boolValue] ?: NO;
kRemoveIndicators = [prefs[@"removeIndicators"] boolValue] ?: NO;
kReExplore = [prefs[@"reExplore"] boolValue] ?: NO;
kAddExplore = [prefs[@"addExplore"] boolValue] ?: NO;
kRemoveShorts = [prefs[@"removeShorts"] boolValue] ?: NO;
kRemoveSubscriptions = [prefs[@"removeSubscriptions"] boolValue] ?: NO;
kRemoveUploads = (prefs[@"removeUploads"] != nil) ? [prefs[@"removeUploads"] boolValue] : YES;
kRemoveLibrary = [prefs[@"removeLibrary"] boolValue] ?: NO;
kCopyVideoInfo = [prefs[@"copyVideoInfo"] boolValue] ?: NO;
kPostManager = [prefs[@"postManager"] boolValue] ?: NO;
kSaveProfilePhoto = [prefs[@"saveProfilePhoto"] boolValue] ?: NO;
kCommentManager = [prefs[@"commentManager"] boolValue] ?: NO;
kFixAlbums = [prefs[@"fixAlbums"] boolValue] ?: NO;
kRemovePlayNext = [prefs[@"removePlayNext"] boolValue] ?: NO;
kRemoveDownloadMenu = [prefs[@"removeDownloadMenu"] boolValue] ?: NO;
kRemoveWatchLaterMenu = [prefs[@"removeWatchLaterMenu"] boolValue] ?: NO;
kRemoveSaveToPlaylistMenu = [prefs[@"removeSaveToPlaylistMenu"] boolValue] ?: NO;
kRemoveShareMenu = [prefs[@"removeShareMenu"] boolValue] ?: NO;
kRemoveNotInterestedMenu = [prefs[@"removeNotInterestedMenu"] boolValue] ?: NO;
kRemoveDontRecommendMenu = [prefs[@"removeDontRecommendMenu"] boolValue] ?: NO;
kRemoveReportMenu = [prefs[@"removeReportMenu"] boolValue] ?: NO;
kNoContinueWatching = [prefs[@"noContinueWatching"] boolValue] ?: NO;
kNoSearchHistory = [prefs[@"noSearchHistory"] boolValue] ?: NO;
kNoRelatedWatchNexts = [prefs[@"noRelatedWatchNexts"] boolValue] ?: NO;
kStickSortComments = [prefs[@"stickSortComments"] boolValue] ?: NO;
kHideSortComments = [prefs[@"hideSortComments"] boolValue] ?: NO;
kPlaylistOldMinibar = [prefs[@"playlistOldMinibar"] boolValue] ?: NO;
kDisableRTL = [prefs[@"disableRTL"] boolValue] ?: NO;
kWiFiQualityIndex = (prefs[@"wifiQualityIndex"] != nil) ? [prefs[@"wifiQualityIndex"] intValue] : 0;
kCellQualityIndex = (prefs[@"cellQualityIndex"] != nil) ? [prefs[@"cellQualityIndex"] intValue] : 0;
kPivotIndex = (prefs[@"pivotIndex"] != nil) ? [prefs[@"pivotIndex"] intValue] : 0;
kAdvancedMode = [prefs[@"advancedMode"] boolValue] ?: NO;
kAdvancedModeReminder = [prefs[@"advancedModeReminder"] boolValue] ?: NO;
NSDictionary *newSettings = @{
@"noAds" : @(kNoAds),
@"backgroundPlayback" : @(kBackgroundPlayback),
@"noCast" : @(kNoCast),
@"removeNotifsButton" : @(kNoNotifsButton),
@"removeSearchButton" : @(kNoSearchButton),
@"removeVoiceSearchButton" : @(kNoVoiceSearchButton),
@"stickyNavbar" : @(kStickyNavbar),
@"noSubbar" : @(kNoSubbar),
@"noYTLogo" : @(kNoYTLogo),
@"hideAutoplay" : @(kHideAutoplay),
@"hideSubs" : @(kHideSubs),
@"noHUDMsgs" : @(kNoHUDMsgs),
@"hidePrevNext" : @(kHidePrevNext),
@"replacePrevNext" : @(kReplacePrevNext),
@"noDarkBg" : @(kNoDarkBg),
@"endScreenCards" : @(kEndScreenCards),
@"noFullscreenActions" : @(kNoFullscreenActions),
@"persistentProgressBar" : @(kPersistentProgressBar),
@"noRelatedVids" : @(kNoRelatedVids),
@"noPromotionCards" : @(kNoPromotionCards),
@"noWatermarks" : @(kNoWatermarks),
@"miniplayer" : @(kMiniplayer),
@"portraitFullscreen" : @(kPortraitFullscreen),
@"copyWithTimestamp" : @(kCopyWithTimestamp),
@"disableAutoplay" : @(kDisableAutoplay),
@"disableAutoCaptions" : @(kDisableAutoCaptions),
@"noContentWarning" : @(kNoContentWarning),
@"classicQuality" : @(kClassicQuality),
@"extraSpeedOptions" : @(kExtraSpeedOptions),
@"dontSnapToChapter" : @(kDontSnapToChapter),
@"redProgressBar" : @(kRedProgressBar),
@"noPlayerRemixButton" : @(kNoPlayerRemixButton),
@"noPlayerClipButton" : @(kNoPlayerClipButton),
@"noPlayerDownloadButton" : @(kNoPlayerDownloadButton),
@"noHints" : @(kNoHints),
@"noFreeZoom" : @(kNoFreeZoom),
@"autoFullscreen" : @(kAutoFullscreen),
@"exitFullscreen" : @(kExitFullscreen),
@"noDoubleTapToSeek" : @(kNoDoubleTapToSeek),
@"shortsOnlyMode" : @(kShortsOnlyMode),
@"hideShorts" : @(kHideShorts),
@"shortsProgress" : @(kShortsProgress),
@"pinchToFullscreenShorts" : @(kPinchToFullscreenShorts),
@"shortsToRegular" : @(kShortsToRegular),
@"resumeShorts" : @(kResumeShorts),
@"hideShortsLogo" : @(kHideShortsLogo),
@"hideShortsSearch" : @(kHideShortsSearch),
@"hideShortsCamera" : @(kHideShortsCamera),
@"hideShortsMore" : @(kHideShortsMore),
@"hideShortsSubscriptions" : @(kHideShortsSubscriptions),
@"hideShortsLike" : @(kHideShortsLike),
@"hideShortsDislike" : @(kHideShortsDislike),
@"hideShortsComments" : @(kHideShortsComments),
@"hideShortsRemix" : @(kHideShortsRemix),
@"hideShortsShare" : @(kHideShortsShare),
@"hideShortsAvatars" : @(kHideShortsAvatars),
@"hideShortsThanks" : @(kHideShortsThanks),
@"hideShortsSource" : @(kHideShortsSource),
@"hideShortsChannelName" : @(kHideShortsChannelName),
@"hideShortsDescription" : @(kHideShortsDescription),
@"hideShortsAudioTrack" : @(kHideShortsAudioTrack),
@"hideShortsPromoCards" : @(kHideShortsPromoCards),
@"removeLabels" : @(kRemoveLabels),
@"removeIndicators" : @(kRemoveIndicators),
@"reExplore" : @(kReExplore),
@"addExplore" : @(kAddExplore),
@"removeShorts" : @(kRemoveShorts),
@"removeSubscriptions" : @(kRemoveSubscriptions),
@"removeUploads" : @(kRemoveUploads),
@"removeLibrary" : @(kRemoveLibrary),
@"copyVideoInfo" : @(kCopyVideoInfo),
@"postManager" : @(kPostManager),
@"saveProfilePhoto" : @(kSaveProfilePhoto),
@"commentManager" : @(kCommentManager),
@"fixAlbums" : @(kFixAlbums),
@"removePlayNext" : @(kRemovePlayNext),
@"removeDownloadMenu" : @(kRemoveDownloadMenu),
@"removeWatchLaterMenu" : @(kRemoveWatchLaterMenu),
@"removeSaveToPlaylistMenu" : @(kRemoveSaveToPlaylistMenu),
@"removeShareMenu" : @(kRemoveShareMenu),
@"removeNotInterestedMenu" : @(kRemoveNotInterestedMenu),
@"removeDontRecommendMenu" : @(kRemoveDontRecommendMenu),
@"removeReportMenu" : @(kRemoveReportMenu),
@"noContinueWatching" : @(kNoContinueWatching),
@"noSearchHistory" : @(kNoSearchHistory),
@"noRelatedWatchNexts" : @(kNoRelatedWatchNexts),
@"stickSortComments" : @(kStickSortComments),
@"hideSortComments" : @(kHideSortComments),
@"playlistOldMinibar" : @(kPlaylistOldMinibar),
@"disableRTL" : @(kDisableRTL),
@"wifiQualityIndex" : @(kWiFiQualityIndex),
@"cellQualityIndex" : @(kCellQualityIndex),
@"pivotIndex" : @(kPivotIndex),
@"advancedMode" : @(kAdvancedMode),
@"advancedModeReminder" : @(kAdvancedModeReminder)
};
if (![newSettings isEqualToDictionary:prefs]) [newSettings writeToFile:path atomically:NO];
}
static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
reloadPrefs();
}
%ctor {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"YTLite.plist"];
NSMutableDictionary *prefs = [NSMutableDictionary dictionaryWithContentsOfFile:path];
if ([prefs[@"shortsOnlyMode"] boolValue] && ([prefs[@"removeShorts"] boolValue] || [prefs[@"reExplore"] boolValue])) {
[prefs setObject:@NO forKey:@"removeShorts"];
[prefs setObject:@NO forKey:@"reExplore"];
[prefs writeToFile:path atomically:NO];
}
if (![prefs[@"advancedMode"] boolValue] && ![prefs[@"advancedModeReminder"] boolValue]) {
[prefs setObject:@(YES) forKey:@"advancedModeReminder"];
[prefs writeToFile:path atomically:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
YTAlertView *alertView = [%c(YTAlertView) confirmationDialogWithAction:^{
[prefs setObject:@(YES) forKey:@"advancedMode"];
[prefs writeToFile:path atomically:NO];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.dvntm.ytlite.prefschanged"), NULL, NULL, YES);
}
actionTitle:LOC(@"Yes")
cancelTitle:LOC(@"No")];
alertView.title = @"YTLite";
alertView.subtitle = [NSString stringWithFormat:LOC(@"AdvancedModeReminder"), @"YTLite", LOC(@"Version"), LOC(@"Advanced")];
[alertView show];
});
}
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.dvntm.ytlite.prefschanged"), NULL, CFNotificationSuspensionBehaviorCoalesce);
reloadPrefs();
}