#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 ytlBool(@"backgroundPlayback") ? YES : NO; } %end %hook MLVideo - (BOOL)playableInBackground { return ytlBool(@"backgroundPlayback") ? YES : NO; } %end // Disable Ads %hook YTIPlayerResponse - (BOOL)isMonetized { return ytlBool(@"noAds") ? NO : YES; } %end %hook YTDataUtils + (id)spamSignalsDictionary { return ytlBool(@"noAds") ? nil : %orig; } + (id)spamSignalsDictionaryWithoutIDFA { return ytlBool(@"noAds") ? nil : %orig; } %end %hook YTAdsInnerTubeContextDecorator - (void)decorateContext:(id)context { if (!ytlBool(@"noAds")) %orig; } %end %hook YTAccountScopedAdsInnerTubeContextDecorator - (void)decorateContext:(id)context { if (!ytlBool(@"noAds")) %orig; } %end %hook YTIElementRenderer - (NSData *)elementData { if (self.hasCompatibilityOptions && self.compatibilityOptions.hasAdLoggingData && ytlBool(@"noAds")) 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 (ytlBool(@"noAds") && [ads containsObject:description]) { return [NSData data]; } NSArray *shortsToRemove = @[@"shorts_shelf.eml", @"shorts_video_cell.eml", @"6Shorts"]; for (NSString *shorts in shortsToRemove) { if (ytlBool(@"hideShorts") && [description containsString:shorts] && ![description containsString:@"history*"]) { return nil; } } return %orig; } %end %hook YTSectionListViewController - (void)loadWithModel:(YTISectionListRenderer *)model { if (ytlBool(@"noAds")) { NSMutableArray *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 ytlBool(@"noCast") ? NO : YES; } - (void)updateRouteButton:(id)arg1 { if (!ytlBool(@"noCast")) %orig; } - (void)updateAllRouteButtons { if (!ytlBool(@"noCast")) %orig; } %end %hook YTSettings - (void)setDisableMDXDeviceDiscovery:(BOOL)arg1 { %orig(ytlBool(@"noCast")); } %end // Hide Navigation Bar Buttons %hook YTRightNavigationButtons - (void)layoutSubviews { %orig; if (ytlBool(@"noNotifsButton")) self.notificationButton.hidden = YES; if (ytlBool(@"noSearchButton")) self.searchButton.hidden = YES; for (UIView *subview in self.subviews) { if (ytlBool(@"noVoiceSearchButton") && [subview.accessibilityLabel isEqualToString:NSLocalizedString(@"search.voice.access", nil)]) subview.hidden = YES; if (ytlBool(@"noCast") && [subview.accessibilityIdentifier isEqualToString:@"id.mdx.playbackroute.button"]) subview.hidden = YES; } } %end %hook YTSearchViewController - (void)viewDidLoad { %orig; if (ytlBool(@"noVoiceSearchButton")) [self setValue:@(NO) forKey:@"_isVoiceSearchAllowed"]; } - (void)setSuggestions:(id)arg1 { if (!ytlBool(@"noSearchHistory")) %orig; } %end %hook YTPersonalizedSuggestionsCacheProvider - (id)activeCache { return ytlBool(@"noSearchHistory") ? nil : %orig; } %end // Remove Videos Section Under Player %hook YTWatchNextResultsViewController - (void)setVisibleSections:(NSInteger)arg1 { arg1 = (ytlBool(@"noRelatedWatchNexts")) ? 1 : arg1; %orig(arg1); } %end %hook YTHeaderView // Stick Navigation bar - (BOOL)stickyNavHeaderEnabled { return ytlBool(@"stickyNavbar") ? YES : %orig; } // Hide YouTube Logo - (void)setCustomTitleView:(UIView *)customTitleView { if (!ytlBool(@"noYTLogo")) %orig; } - (void)setTitle:(NSString *)title { ytlBool(@"noYTLogo") ? %orig(@"") : %orig; } %end // Premium logo %hook UIImageView - (void)setImage:(UIImage *)image { if (!ytlBool(@"premiumYTLogo")) return %orig; NSString *resourcesPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Frameworks/Module_Framework.framework/Innertube_Resources.bundle"]; NSBundle *frameworkBundle = [NSBundle bundleWithPath:resourcesPath]; if ([[image description] containsString:@"Resources: youtube_logo)"]) { image = [UIImage imageNamed:@"youtube_premium_logo" inBundle:frameworkBundle compatibleWithTraitCollection:nil]; } else if ([[image description] containsString:@"Resources: youtube_logo_dark)"]) { image = [UIImage imageNamed:@"youtube_premium_logo_white" inBundle:frameworkBundle compatibleWithTraitCollection:nil]; } %orig(image); } %end // Remove Subbar %hook YTMySubsFilterHeaderView - (void)setChipFilterView:(id)arg1 { if (!ytlBool(@"noSubbar")) %orig; } %end %hook YTHeaderContentComboView - (void)enableSubheaderBarWithView:(id)arg1 { if (!ytlBool(@"noSubbar")) %orig; } - (void)setFeedHeaderScrollMode:(int)arg1 { ytlBool(@"noSubbar") ? %orig(0) : %orig; } %end %hook YTChipCloudCell - (void)layoutSubviews { if (self.superview && ytlBool(@"noSubbar")) { [self removeFromSuperview]; } %orig; } %end %hook YTMainAppControlsOverlayView // Hide Autoplay Switch - (void)setAutoplaySwitchButtonRenderer:(id)arg1 { if (!ytlBool(@"hideAutoplay")) %orig; } // Hide Subs Button - (void)setClosedCaptionsOrSubtitlesButtonAvailable:(BOOL)arg1 { ytlBool(@"hideSubs") ? %orig(NO) : %orig; } // Pause On Overlay - (void)setOverlayVisible:(BOOL)visible { %orig; if (!ytlBool(@"pauseOnOverlay")) return; visible ? [self.playerViewController pause] : [self.playerViewController play]; } %end // Remove HUD Messages %hook YTHUDMessageView - (id)initWithMessage:(id)arg1 dismissHandler:(id)arg2 { return ytlBool(@"noHUDMsgs") ? nil : %orig; } %end %hook YTColdConfig // Hide Next & Previous buttons - (BOOL)removeNextPaddleForSingletonVideos { return ytlBool(@"hidePrevNext") ? YES : %orig; } - (BOOL)removePreviousPaddleForSingletonVideos { return ytlBool(@"hidePrevNext") ? YES : %orig; } // Replace Next & Previous with Fast Forward & Rewind buttons - (BOOL)replaceNextPaddleWithFastForwardButtonForSingletonVods { return ytlBool(@"replacePrevNext") ? YES : %orig; } - (BOOL)replacePreviousPaddleWithRewindButtonForSingletonVods { return ytlBool(@"replacePrevNext") ? YES : %orig; } // Disable Free Zoom - (BOOL)videoZoomFreeZoomEnabledGlobalConfig { return ytlBool(@"noFreeZoom") ? NO : %orig; } // Stick Sort Buttons in Comments Section - (BOOL)enableHideChipsInTheCommentsHeaderOnScrollIos { return ytlBool(@"stickSortComments") ? NO : %orig; } // Hide Sort Buttons in Comments Section - (BOOL)enableChipsInTheCommentsHeaderIos { return ytlBool(@"hideSortComments") ? 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 ytlBool(@"playlistOldMinibar") ? NO : %orig; } %end // Remove Dark Background in Overlay %hook YTMainAppVideoPlayerOverlayView - (void)setBackgroundVisible:(BOOL)arg1 isGradientBackground:(BOOL)arg2 { ytlBool(@"noDarkBg") ? %orig(NO, arg2) : %orig; } %end // No Endscreen Cards %hook YTCreatorEndscreenView - (void)setHidden:(BOOL)arg1 { ytlBool(@"endScreenCards") ? %orig(YES) : %orig; } %end // Disable Fullscreen Actions %hook YTFullscreenActionsView - (BOOL)enabled { return ytlBool(@"noFullscreenActions") ? NO : YES; } - (void)setEnabled:(BOOL)arg1 { ytlBool(@"noFullscreenActions") ? %orig(NO) : %orig; } %end // Dont Show Related Videos on Finish %hook YTFullscreenEngagementOverlayController - (void)setRelatedVideosVisible:(BOOL)arg1 { ytlBool(@"noRelatedVids") ? %orig(NO) : %orig; } %end // Hide Paid Promotion Cards %hook YTMainAppVideoPlayerOverlayViewController - (void)setPaidContentWithPlayerData:(id)data { if (!ytlBool(@"noPromotionCards")) %orig; } - (void)playerOverlayProvider:(YTPlayerOverlayProvider *)provider didInsertPlayerOverlay:(YTPlayerOverlay *)overlay { if ([[overlay overlayIdentifier] isEqualToString:@"player_overlay_paid_content"] && ytlBool(@"noPromotionCards")) return; %orig; } %end %hook YTInlineMutedPlaybackPlayerOverlayViewController - (void)setPaidContentWithPlayerData:(id)data { if (!ytlBool(@"noPromotionCards")) %orig; } %end %hook YTInlinePlayerBarContainerView - (void)setPlayerBarAlpha:(CGFloat)alpha { ytlBool(@"persistentProgressBar") ? %orig(1.0) : %orig; } %end // Remove Watermarks %hook YTAnnotationsViewController - (void)loadFeaturedChannelWatermark { if (!ytlBool(@"noWatermarks")) %orig; } %end %hook YTMainAppVideoPlayerOverlayView - (BOOL)isWatermarkEnabled { return ytlBool(@"noWatermarks") ? NO : %orig; } %end // Forcibly Enable Miniplayer %hook YTWatchMiniBarViewController - (void)updateMiniBarPlayerStateFromRenderer { if (!ytlBool(@"miniplayer")) %orig; } %end // Portrait Fullscreen %hook YTWatchViewController - (unsigned long long)allowedFullScreenOrientations { return ytlBool(@"portraitFullscreen") ? UIInterfaceOrientationMaskAllButUpsideDown : %orig; } %end // Disable Autoplay %hook YTPlaybackConfig - (void)setStartPlayback:(BOOL)arg1 { ytlBool(@"disableAutoplay") ? %orig(NO) : %orig; } %end // Skip Content Warning (https://github.com/qnblackcat/uYouPlus/blob/main/uYouPlus.xm#L452-L454) %hook YTPlayabilityResolutionUserActionUIController - (void)showConfirmAlert { ytlBool(@"noContentWarning") ? [self confirmAlertDidPressConfirm] : %orig; } %end // Classic Video Quality (https://github.com/PoomSmart/YTClassicVideoQuality) %hook YTVideoQualitySwitchControllerFactory - (id)videoQualitySwitchControllerWithParentResponder:(id)responder { Class originalClass = %c(YTVideoQualitySwitchOriginalController); return ytlBool(@"classicQuality") && 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 (ytlBool(@"extraSpeedOptions")) [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 (!ytlBool(@"classicQuality") && !ytlBool(@"extraSpeedOptions") && [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 (ytlBool(@"dontSnapToChapter")) self.enableSnapToChapter = NO; } %end // Red Progress Bar and Gray Buffer Progress %hook YTInlinePlayerBarContainerView - (id)quietProgressBarColor { return ytlBool(@"redProgressBar") ? [UIColor redColor] : %orig; } %end %hook YTSegmentableInlinePlayerBarView - (void)setBufferedProgressBarColor:(id)arg1 { if (ytlBool(@"redProgressBar")) %orig([UIColor colorWithRed:0.65 green:0.65 blue:0.65 alpha:0.60]); } %end // Disable Hints %hook YTSettings - (BOOL)areHintsDisabled { return ytlBool(@"noHints") ? YES : NO; } - (void)setHintsDisabled:(BOOL)arg1 { ytlBool(@"noHints") ? %orig(YES) : %orig; } %end %hook YTUserDefaults - (BOOL)areHintsDisabled { return ytlBool(@"noHints") ? YES : NO; } - (void)setHintsDisabled:(BOOL)arg1 { ytlBool(@"noHints") ? %orig(YES) : %orig; } %end void addEndTime(YTPlayerViewController *self, YTSingleVideoController *video, YTSingleVideoTime *time) { if (!ytlBool(@"videoEndTime")) return; CGFloat rate = video.playbackRate != 0 ? video.playbackRate : 1.0; NSTimeInterval remainingTime = (lround(video.totalMediaTime) - lround(time.time)) / rate; NSDate *estimatedEndTime = [NSDate dateWithTimeIntervalSinceNow:remainingTime]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; [dateFormatter setDateFormat:ytlBool(@"24hrFormat") ? @"HH:mm" : @"h:mm a"]; NSString *formattedEndTime = [dateFormatter stringFromDate:estimatedEndTime]; YTPlayerView *playerView = (YTPlayerView *)self.view; if (![playerView.overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) return; YTMainAppVideoPlayerOverlayView *overlay = (YTMainAppVideoPlayerOverlayView*)playerView.overlayView; YTLabel *durationLabel = overlay.playerBar.durationLabel; overlay.playerBar.endTimeString = formattedEndTime; if (![durationLabel.text containsString:formattedEndTime]) { durationLabel.text = [durationLabel.text stringByAppendingString:[NSString stringWithFormat:@" • %@", formattedEndTime]]; [durationLabel sizeToFit]; } } %hook YTPlayerViewController - (void)loadWithPlayerTransition:(id)arg1 playbackConfig:(id)arg2 { %orig; if (ytlInt(@"wiFiQualityIndex") != 0 || ytlInt(@"cellQualityIndex") != 0) [self performSelector:@selector(autoQuality) withObject:nil afterDelay:1.0]; if (ytlBool(@"autoFullscreen")) [self performSelector:@selector(autoFullscreen) withObject:nil afterDelay:0.75]; if (ytlBool(@"shortsToRegular")) [self performSelector:@selector(shortsToRegular) withObject:nil afterDelay:0.75]; if (ytlInt(@"autoSpeedIndex") != 3) [self performSelector:@selector(setAutoSpeed) withObject:nil afterDelay:0.75]; if (ytlBool(@"disableAutoCaptions")) [self performSelector:@selector(turnOffCaptions) withObject:nil afterDelay:1.0]; } %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 { if ([self.view.superview isKindOfClass:NSClassFromString(@"YTWatchView")]) { [self setActiveCaptionTrack:nil]; } } %new - (void)setAutoSpeed { if ([self.activeVideoPlayerOverlay isKindOfClass:NSClassFromString(@"YTMainAppVideoPlayerOverlayViewController")] && [self.view.superview isKindOfClass:NSClassFromString(@"YTWatchView")]) { YTMainAppVideoPlayerOverlayViewController *overlayVC = (YTMainAppVideoPlayerOverlayViewController *)self.activeVideoPlayerOverlay; NSArray *speedLabels = @[@0.25, @0.5, @0.75, @1.0, @1.25, @1.5, @1.75, @2.0, @3.0, @4.0, @5.0]; [overlayVC setPlaybackRate:[speedLabels[ytlInt(@"autoSpeedIndex")] floatValue]]; } } %new - (void)autoQuality { if (![self.view.superview isKindOfClass:NSClassFromString(@"YTWatchView")]) { return; } NetworkStatus status = [[Reachability reachabilityForInternetConnection] currentReachabilityStatus]; NSInteger kQualityIndex = status == ReachableViaWiFi ? ytlInt(@"wiFiQualityIndex") : ytlInt(@"cellQualityIndex"); NSString *bestQualityLabel; int highestResolution = 0; for (MLFormat *format in self.activeVideo.selectableVideoFormats) { int reso = format.singleDimensionResolution; if (reso > highestResolution) { highestResolution = reso; bestQualityLabel = format.qualityLabel; } } NSArray *qualityLabels = @[@"Default", bestQualityLabel, @"2160p60", @"2160p", @"1440p60", @"1440p", @"1080p60", @"1080p", @"720p60", @"720p", @"480p", @"360p"]; NSString *qualityLabel = qualityLabels[kQualityIndex]; 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]]; } } - (void)singleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { %orig; addEndTime(self, video, time); } - (void)potentiallyMutatedSingleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { %orig; addEndTime(self, video, time); } %end %hook YTInlinePlayerBarContainerView %property (nonatomic, strong) NSString *endTimeString; - (void)setPeekableViewVisible:(BOOL)visible { %orig; if (!ytlBool(@"videoEndTime")) return; if (self.endTimeString && ![self.durationLabel.text containsString:self.endTimeString]) { self.durationLabel.text = [self.durationLabel.text stringByAppendingString:[NSString stringWithFormat:@" • %@", self.endTimeString]]; [self.durationLabel sizeToFit]; } } %end // Exit Fullscreen on Finish %hook YTWatchFlowController - (BOOL)shouldExitFullScreenOnFinish { return ytlBool(@"exitFullscreen") ? YES : NO; } %end %hook YTMainAppVideoPlayerOverlayViewController // Disable Double Tap To Seek - (BOOL)allowDoubleTapToSeekGestureRecognizer { return ytlBool(@"noDoubleTapToSeek") ? NO : %orig; } // Disable Two Finger Double Tap - (BOOL)allowTwoFingerDoubleTapGestureRecognizer { return ytlBool(@"noTwoFingerSnapToChapter") ? NO : %orig; } // Copy Timestamped Link by Pressing On Pause - (void)didPressPause:(id)arg1 { %orig; if (ytlBool(@"copyWithTimestamp")) { 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 - (UILabel *)titleLabel { UILabel *label = %orig; if ([self.accessibilityIdentifier isEqualToString:@"id.playlist.playall.button"]) { label.adjustsFontSizeToFitWidth = YES; } return label; } %end // Fit Shorts Button Labels For Localizations %hook YTReelPlayerButton - (UILabel *)titleLabel { UILabel *label = %orig; label.adjustsFontSizeToFitWidth = YES; return label; } %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 (ytlBool(@"removePlayNext") && 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": @(ytlBool(@"removeDownloadMenu")), @"1": @(ytlBool(@"removeWatchLaterMenu")), @"3": @(ytlBool(@"removeSaveToPlaylistMenu")), @"5": @(ytlBool(@"removeShareMenu")), @"12": @(ytlBool(@"removeNotInterestedMenu")), @"31": @(ytlBool(@"removeDontRecommendMenu")), @"58": @(ytlBool(@"removeReportMenu")) }; if (![actionsToRemove[identifier] boolValue]) { %orig; } } %end // Hide buttons under the video player (@PoomSmart) static BOOL findCell(ASNodeController *nodeController, NSArray *identifiers) { for (id child in [nodeController children]) { if ([child isKindOfClass:%c(ELMNodeController)]) { NSArray *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 (ytlBool(@"noPlayerRemixButton") && findCell(nodeController, @[@"id.video.remix.button"])) { return CGSizeZero; } if (ytlBool(@"noPlayerClipButton") && findCell(nodeController, @[@"clip_button.eml"])) { return CGSizeZero; } if (ytlBool(@"noPlayerDownloadButton") && 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"]) && ytlBool(@"hideShorts"))) { [self removeCellsAtIndexPath:indexPath]; } } } else if (([cell isKindOfClass:objc_lookUpClass("YTReelShelfCell")] && ytlBool(@"hideShorts")) || ([cell isKindOfClass:objc_lookUpClass("YTHorizontalCardListCell")] && ytlBool(@"noContinueWatching"))) { [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 ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)shouldAlwaysEnablePlayerBar { return ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)shouldEnablePlayerBarOnlyOnPause { return ytlBool(@"shortsProgress") ? NO : YES; } %end %hook YTReelPlayerViewControllerSub - (BOOL)shouldEnablePlayerBar { return ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)shouldAlwaysEnablePlayerBar { return ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)shouldEnablePlayerBarOnlyOnPause { return ytlBool(@"shortsProgress") ? NO : YES; } %end %hook YTShortsPlayerViewController - (BOOL)shouldAlwaysEnablePlayerBar { return ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)shouldEnablePlayerBarOnlyOnPause { return ytlBool(@"shortsProgress") ? NO : YES; } %end %hook YTColdConfig - (BOOL)iosEnableVideoPlayerScrubber { return ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)mobileShortsTabInlined { return ytlBool(@"shortsProgress") ? YES : NO; } - (BOOL)iosUseSystemVolumeControlInFullscreen { return ytlBool(@"stockVolumeHUD") ? YES : NO; } %end %hook YTHotConfig - (BOOL)enablePlayerBarForVerticalVideoWhenControlsHiddenInFullscreen { return ytlBool(@"shortsProgress") ? YES : NO; } %end // Dont Startup Shorts %hook YTShortsStartupCoordinator - (id)evaluateResumeToShorts { return ytlBool(@"resumeShorts") ? nil : %orig; } %end // Hide Shorts Elements %hook YTReelPausedStateCarouselView - (void)setPausedStateCarouselVisible:(BOOL)arg1 animated:(BOOL)arg2 { ytlBool(@"hideShortsSubscriptions") ? %orig(arg1 = NO, arg2) : %orig; } %end %hook YTReelWatchPlaybackOverlayView - (void)setReelLikeButton:(id)arg1 { if (!ytlBool(@"hideShortsLike")) %orig; } - (void)setReelDislikeButton:(id)arg1 { if (!ytlBool(@"hideShortsDislike")) %orig; } - (void)setViewCommentButton:(id)arg1 { if (!ytlBool(@"hideShortsComments")) %orig; } - (void)setRemixButton:(id)arg1 { if (!ytlBool(@"hideShortsRemix")) %orig; } - (void)setShareButton:(id)arg1 { if (!ytlBool(@"hideShortsShare")) %orig; } - (void)setNativePivotButton:(id)arg1 { if (!ytlBool(@"hideShortsAvatars")) %orig; } - (void)setPivotButtonElementRenderer:(id)arg1 { if (!ytlBool(@"hideShortsAvatars")) %orig; } %end %hook YTReelHeaderView - (void)setTitleLabelVisible:(BOOL)arg1 animated:(BOOL)arg2 { ytlBool(@"hideShortsLogo") ? %orig(arg1 = NO, arg2) : %orig; } %end %hook YTReelTransparentStackView - (void)layoutSubviews { %orig; for (YTQTMButton *button in self.subviews) { if ([button respondsToSelector:@selector(buttonRenderer)]) { if (ytlBool(@"hideShortsSearch") && button.buttonRenderer.icon.iconType == 1045) button.hidden = YES; if (ytlBool(@"hideShortsCamera") && button.buttonRenderer.icon.iconType == 1046) button.hidden = YES; if (ytlBool(@"hideShortsMore") && button.buttonRenderer.icon.iconType == 1047) button.hidden = YES; } } } %end %hook YTReelWatchHeaderView - (void)setChannelBarElementRenderer:(id)renderer { if (!ytlBool(@"hideShortsChannelName")) %orig; } - (void)setHeaderRenderer:(id)renderer { if (!ytlBool(@"hideShortsDescription")) %orig; } - (void)setShortsVideoTitleElementRenderer:(id)renderer { if (!ytlBool(@"hideShortsDescription")) %orig; } - (void)setSoundMetadataElementRenderer:(id)renderer { if (!ytlBool(@"hideShortsAudioTrack")) %orig; } - (void)setActionElement:(id)renderer { if (!ytlBool(@"hideShortsPromoCards")) %orig; } - (void)setBadgeRenderer:(id)renderer { if (!ytlBool(@"hideShortsThanks")) %orig; } - (void)setMultiFormatLinkElementRenderer:(id)renderer { if (!ytlBool(@"hideShortsSource")) %orig; } %end static BOOL isOverlayShown = YES; %hook YTPlayerView - (void)didPinch:(UIPinchGestureRecognizer *)gesture { %orig; if (ytlBool(@"pinchToFullscreenShorts") && [self.playerViewDelegate.parentViewController isKindOfClass:NSClassFromString(@"YTShortsPlayerViewController")]) { YTShortsPlayerViewController *shortsPlayerVC = (YTShortsPlayerViewController *)self.playerViewDelegate.parentViewController; YTReelContentView *contentView = (YTReelContentView *)shortsPlayerVC.view; UIWindow *mainWindow = [UIApplication sharedApplication].windows.firstObject; YTAppViewController *appVC = (YTAppViewController *)mainWindow.rootViewController; if (gesture.scale > 1) { if (!ytlBool(@"shortsOnlyMode")) [appVC hidePivotBar]; [UIView animateWithDuration:0.3 animations:^{ contentView.playbackOverlay.alpha = 0; isOverlayShown = contentView.playbackOverlay.alpha; }]; } else { if (!ytlBool(@"shortsOnlyMode")) [appVC showPivotBar]; [UIView animateWithDuration:0.3 animations:^{ contentView.playbackOverlay.alpha = 1; isOverlayShown = contentView.playbackOverlay.alpha; }]; } } } %end %hook YTReelContentView - (void)setPlaybackView:(id)arg1 { %orig; self.playbackOverlay.alpha = isOverlayShown; if (ytlBool(@"shortsOnlyMode")) { 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) { ytlSetBool(NO, @"shortsOnlyMode"); [[%c(YTToastResponderEvent) eventWithMessage:LOC(@"ShortsModeTurnedOff") firstResponder:[%c(YTUIUtils) topViewControllerForPresenting]] send]; UIWindow *mainWindow = [UIApplication sharedApplication].windows.firstObject; YTAppViewController *appVC = (YTAppViewController *)mainWindow.rootViewController; [appVC performSelector:@selector(showPivotBar) withObject:nil afterDelay:1.0]; } } %end %hook YTSegmentableInlinePlayerBarView - (void)setScrubberTime:(CGFloat)time { %orig; if (ytlBool(@"autoSkipShorts") && round(time) == round(self.totalTime)) { UIWindow *mainWindow = [UIApplication sharedApplication].windows.firstObject; YTAppViewController *appVC = (YTAppViewController *)mainWindow.rootViewController; [appVC.pivotBarViewController.scrubberDelegate performSelector:@selector(reelContentViewRequestsAdvanceToNextVideo:)]; } } %end static void downloadImageFromURL(UIResponder *responder, NSURL *URL, BOOL download) { NSString *URLString = URL.absoluteString; if (ytlBool(@"fixAlbums") && [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 (ytlBool(@"commentManager") && [[self valueForKey:@"_accessibilityIdentifier"] isEqualToString:@"id.comment.content.label"]) { if ([self isKindOfClass:NSClassFromString(@"ASTextNode")]) { ASTextNode *textNode = (ASTextNode *)self; NSString *comment; if ([textNode respondsToSelector:@selector(attributedText)]) { if (textNode.attributedText) 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 (ytlBool(@"postManager") && [self isKindOfClass:NSClassFromString(@"ELMExpandableTextNode")]) { ELMExpandableTextNode *expandableTextNode = (ELMExpandableTextNode *)self; if ([expandableTextNode.currentTextNode isKindOfClass:NSClassFromString(@"ASTextNode")]) { ASTextNode *textNode = (ASTextNode *)expandableTextNode.currentTextNode; NSString *text; if ([textNode respondsToSelector:@selector(attributedText)]) { if (textNode.attributedText) 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 (ytlBool(@"postManager") && 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": @(ytlBool(@"postManager"))}, @{@"selector": @"savePFP:", @"text": @"ELMImageNode-View", @"key": @(ytlBool(@"saveProfilePhoto"))}, @{@"selector": @"commentManager:", @"text": @"id.ui.comment_cell", @"key": @(ytlBool(@"commentManager"))} ]; 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 colorWithRed:0.75 green:0.50 blue:0.90 alpha:1.0] iconImage:YTImageNamed(@"yt_outline_image_24pt") iconColor:[UIColor colorWithRed:0.75 green:0.50 blue:0.90 alpha:1.0] 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 colorWithRed:0.75 green:0.50 blue:0.90 alpha:1.0] iconImage:YTImageNamed(@"yt_outline_library_image_24pt") iconColor:[UIColor colorWithRed:0.75 green:0.50 blue:0.90 alpha:1.0] 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 *items = [renderer itemsArray]; NSDictionary *identifiersToRemove = @{ @"FEshorts": @[@(ytlBool(@"removeShorts")), @(ytlBool(@"reExplore"))], @"FEsubscriptions": @[@(ytlBool(@"removeSubscriptions"))], @"FEuploads": @[@(ytlBool(@"removeUploads"))], @"FElibrary": @[@(ytlBool(@"removeLibrary"))] }; 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 && (ytlBool(@"reExplore") || ytlBool(@"addExplore"))) { 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(ytlBool(@"removeIndicators") ? [UIColor clearColor] : arg1); } - (void)setBorderColor:(id)arg1 { %orig(ytlBool(@"removeIndicators") ? [UIColor clearColor] : arg1); } %end // Hide Tab Labels %hook YTPivotBarItemView - (void)setRenderer:(YTIPivotBarRenderer *)renderer { %orig; if (ytlBool(@"removeLabels")) { [self.navigationButton setTitle:@"" forState:UIControlStateNormal]; [self.navigationButton setSizeWithPaddingAndInsets:NO]; } UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(manageTab:)]; longPress.minimumPressDuration = 0.3; if ([self.renderer.pivotIdentifier isEqualToString:@"FEwhat_to_watch"]) [self addGestureRecognizer:longPress]; } %new - (void)manageTab:(UILongPressGestureRecognizer *)gesture { if (gesture.state == UIGestureRecognizerStateBegan) { ytlBool(@"removeLibrary") ? ytlSetBool(NO, @"removeLibrary") : ytlSetBool(YES, @"removeLibrary"); [[[%c(YTHeaderContentComboViewController) alloc] init] refreshPivotBar]; [[%c(YTToastResponderEvent) eventWithMessage:ytlBool(@"removeLibrary") ? LOC(@"LibraryRemoved") : LOC(@"LibraryAdded") firstResponder:self.delegate] send]; } } %end // Startup Tab BOOL isTabSelected = NO; %hook YTPivotBarViewController - (void)viewDidAppear:(BOOL)animated { %orig; if (!isTabSelected && !ytlBool(@"shortsOnlyMode")) { NSArray *pivotIdentifiers = @[@"FEwhat_to_watch", @"FEexplore", @"FEshorts", @"FEsubscriptions", @"FElibrary"]; [self selectItemWithPivotIdentifier:pivotIdentifiers[ytlInt(@"pivotIndex")]]; isTabSelected = YES; } if (ytlBool(@"shortsOnlyMode")) { [self selectItemWithPivotIdentifier:@"FEshorts"]; [self.parentViewController hidePivotBar]; } } %end %hook YTAppViewController - (void)showPivotBar { if (!ytlBool(@"shortsOnlyMode")) { %orig; isOverlayShown = YES; } } %end %hook YTReelWatchRootViewController - (void)viewDidAppear:(BOOL)animated { %orig; if (ytlBool(@"shortsOnlyMode")) { [self.navigationController.parentViewController hidePivotBar]; } } %end %hook YTEngagementPanelView - (void)layoutSubviews { %orig; if (ytlBool(@"copyVideoInfo") && [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 CGFloat rateBeforeSpeedmaster = 1.0; static void manageSpeedmasterYTLite(UILongPressGestureRecognizer *gesture, YTMainAppVideoPlayerOverlayViewController *delegate, YTInlinePlayerScrubUserEducationView *edu) { NSArray *speedLabels = @[@0, @2.0, @0.25, @0.5, @0.75, @1.0, @1.25, @1.5, @1.75, @2.0, @3.0, @4.0, @5.0]; YTLabel *label = [edu valueForKey:@"_userEducationLabel"]; edu.labelType = 1; [label setValue:[NSString stringWithFormat:@"%@: %@×", LOC(@"PlaybackSpeed"), speedLabels[ytlInt(@"speedIndex")]] forKey:@"text"]; if (gesture.state == UIGestureRecognizerStateBegan) { rateBeforeSpeedmaster = delegate.currentPlaybackRate; [delegate setPlaybackRate:[speedLabels[ytlInt(@"speedIndex")] floatValue]]; [edu setVisible:YES]; } else if (gesture.state == UIGestureRecognizerStateEnded) { [delegate setPlaybackRate:rateBeforeSpeedmaster]; [edu setVisible:NO]; } } %hook YTMainAppVideoPlayerOverlayView - (void)setSeekAnywherePanGestureRecognizer:(id)arg1 { if (ytlInt(@"speedIndex") == 0) return %orig; UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(speedmasterYtLite:)]; longPress.minimumPressDuration = 0.3; if (ytlInt(@"speedIndex") != 0) [self addGestureRecognizer:longPress]; } %new - (void)speedmasterYtLite:(UILongPressGestureRecognizer *)gesture { YTInlinePlayerScrubUserEducationView *edu = self.scrubUserEducationView; manageSpeedmasterYTLite(gesture, self.delegate, edu); } %end %hook YTSpeedmasterController - (void)speedmasterDidLongPressWithRecognizer:(UILongPressGestureRecognizer *)gesture { if (ytlInt(@"speedIndex") == 0) return; if (ytlInt(@"speedIndex") == 1) return %orig; YTMainAppVideoPlayerOverlayViewController *delegate = [self valueForKey:@"_delegate"]; YTInlinePlayerScrubUserEducationView *edu = (YTInlinePlayerScrubUserEducationView *)delegate.videoPlayerOverlayView.scrubUserEducationView; manageSpeedmasterYTLite(gesture, delegate, edu); } %end // Disable Right-To-Left Formatting %hook NSParagraphStyle + (NSWritingDirection)defaultWritingDirectionForLanguage:(id)lang { return ytlBool(@"disableRTL") ? NSWritingDirectionLeftToRight : %orig; } + (NSWritingDirection)_defaultWritingDirection { return ytlBool(@"disableRTL") ? NSWritingDirectionLeftToRight : %orig; } %end // Fix Albums For Russian Users static NSURL *newCoverURL(NSURL *originalURL) { NSDictionary *hostsToReplace = @{ @"yt3.ggpht.com": @"yt4.ggpht.com", @"yt3.googleusercontent.com": @"yt4.googleusercontent.com", }; NSString *const replacement = hostsToReplace[originalURL.host]; if (ytlBool(@"fixAlbums") && replacement) { NSURLComponents *components = [NSURLComponents componentsWithURL:originalURL resolvingAgainstBaseURL:NO]; components.host = replacement; return components.URL; } return originalURL; } %hook YTImageSelectionStrategyImageURLs - (id)initWithSelectedImageURL:(NSURL *)selectedImageURL updatedImageURL:(NSURL *)updatedImageURL { return %orig(newCoverURL(selectedImageURL), newCoverURL(updatedImageURL)); } %end // %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 %ctor { if (ytlBool(@"shortsOnlyMode") && (ytlBool(@"removeShorts") || ytlBool(@"reExplore"))) { ytlSetBool(NO, @"removeShorts"); ytlSetBool(NO, @"reExplore"); } if (!ytlBool(@"advancedMode") && !ytlBool(@"advancedModeReminder")) { ytlSetBool(YES, @"advancedModeReminder"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ YTAlertView *alertView = [%c(YTAlertView) confirmationDialogWithAction:^{ ytlSetBool(YES, @"advancedMode"); } actionTitle:LOC(@"Yes") cancelTitle:LOC(@"No")]; alertView.title = @"YTLite"; alertView.subtitle = [NSString stringWithFormat:LOC(@"AdvancedModeReminder"), @"YTLite", LOC(@"Version"), LOC(@"Advanced")]; [alertView show]; }); } }