Files
YTLitePlus/Tweaks/iSponsorBlock/iSponsorBlock.xm
2023-06-27 09:54:41 +02:00

1048 lines
54 KiB
Plaintext

#import "Headers/iSponsorBlock.h"
#import <AudioToolbox/AudioToolbox.h>
#import <rootless.h>
#import "Headers/ColorFunctions.h"
#import "Headers/SponsorBlockSettingsController.h"
#import "Headers/SponsorBlockRequest.h"
#import "Headers/SponsorBlockViewController.h"
#define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil]
extern "C" NSBundle *iSponsorBlockBundle() {
static NSBundle *bundle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:@"iSponsorBlock" ofType:@"bundle"];
if (tweakBundlePath)
bundle = [NSBundle bundleWithPath:tweakBundlePath];
else
bundle = [NSBundle bundleWithPath:ROOT_PATH_NS("/Library/Application Support/iSponsorBlock.bundle")];
});
return bundle;
}
NSBundle *tweakBundle = iSponsorBlockBundle();
// Sound effect for skip segments
static void playSponsorAudio() {
NSString *audioFilePath = [tweakBundle pathForResource:@"SponsorAudio" ofType:@"m4a"];
NSURL *audioFileURL = [NSURL fileURLWithPath:audioFilePath];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)audioFileURL, &soundID);
AudioServicesPlaySystemSound(soundID);
}
// Check and translate segment title for HUD
NSDictionary *categoryLocalization = @{
@"sponsor": LOC(@"sponsor"),
@"intro": LOC(@"intro"),
@"outro": LOC(@"outro"),
@"interaction": LOC(@"interaction"),
@"selfpromo": LOC(@"selfpromo"),
@"music_offtopic": LOC(@"music_offtopic")
};
%group Main
NSString *modifiedTimeString;
%hook YTPlayerViewController
%property (strong, nonatomic) NSMutableArray *skipSegments;
%property (nonatomic, assign) NSInteger currentSponsorSegment;
%property (strong, nonatomic) MBProgressHUD *hud;
%property (nonatomic, assign) NSInteger unskippedSegment;
%property (strong, nonatomic) NSMutableArray *userSkipSegments;
%property (strong, nonatomic) NSString *channelID;
%property (nonatomic, assign) BOOL hudDisplayed;
// used to keep support for older versions, as seekToTime is new
%new
- (void)isb_scrubToTime:(CGFloat)time {
// YT v17.30.1 switched scrubToTime to seekToTime
[self respondsToSelector:@selector(scrubToTime:)] ? [self scrubToTime:time] : [self seekToTime:time];
}
- (void)singleVideo:(id)arg1 currentVideoTimeDidChange:(YTSingleVideoTime *)arg2 {
%orig;
YTPlayerView *playerView = (YTPlayerView *)self.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if (!self.channelID) self.channelID = @"";
if (self.skipSegments.count > 0 && [overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)] && ![kWhitelistedChannels containsObject:self.channelID]) {
if (kShowModifiedTime) {
UILabel *durationLabel = overlayView.playerBar.durationLabel;
if (![durationLabel.text containsString:modifiedTimeString]) durationLabel.text = [NSString stringWithFormat:@"%@ (%@)", durationLabel.text, modifiedTimeString];
[durationLabel sizeToFit];
}
SponsorSegment *sponsorSegment = [[SponsorSegment alloc] initWithStartTime:-1 endTime:-1 category:nil UUID:nil];
if (self.currentSponsorSegment <= self.skipSegments.count-1) {
sponsorSegment = self.skipSegments[self.currentSponsorSegment];
} else if (self.unskippedSegment != self.currentSponsorSegment-1) {
sponsorSegment = self.skipSegments[self.currentSponsorSegment-1];
}
if ((lroundf(arg2.time) == ceil(sponsorSegment.startTime) && arg2.time >= sponsorSegment.startTime) || (lroundf(arg2.time) >= ceil(sponsorSegment.startTime) && arg2.time < sponsorSegment.endTime)) {
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 3) {
if (self.hud.superview != self.view && self.hudDisplayed == NO) {
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
self.hudDisplayed = YES; // Set yes to make sure that HUD is not persistent (Issue #62)
self.hud.mode = MBProgressHUDModeCustomView;
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
NSString *localizedManualSkip = LOC(@"ManuallySkipReminder");
NSString *formattedManualSkip = [NSString stringWithFormat:localizedManualSkip, localizedSegment, lroundf(sponsorSegment.startTime)/60, lroundf(sponsorSegment.startTime)%60, lroundf(sponsorSegment.endTime)/60, lroundf(sponsorSegment.endTime)%60];
self.hud.label.text = formattedManualSkip;
self.hud.label.numberOfLines = 0;
[self.hud.button setTitle:LOC(@"Skip") forState:UIControlStateNormal];
[self.hud.button addTarget:self action:@selector(manuallySkipSegment:) forControlEvents:UIControlEventTouchUpInside];
// Add custom button to hide HUD
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
UIImage *cancelImage = [[UIImage systemImageNamed:@"x.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[cancelButton setImage:cancelImage forState:UIControlStateNormal];
[cancelButton setTintColor:[[UIColor blackColor] colorWithAlphaComponent:0.7]];
[cancelButton addTarget:self action:@selector(cancelHUD:) forControlEvents:UIControlEventTouchUpInside];
UIView *buttonSuperview = self.hud.button.superview;
[buttonSuperview addSubview:cancelButton];
CGFloat buttonSpacing = 10.0;
cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[cancelButton.topAnchor constraintEqualToAnchor:self.hud.button.topAnchor],
[cancelButton.leadingAnchor constraintEqualToAnchor:self.hud.button.trailingAnchor constant:buttonSpacing],
[cancelButton.heightAnchor constraintEqualToAnchor:self.hud.button.heightAnchor]
]];
self.hud.offset = CGPointMake(self.view.frame.size.width, -MBProgressMaxOffset);
// Use a delay equal to the length of the sponsored segment to avoid HUD call
double delayInSeconds = sponsorSegment.endTime - sponsorSegment.startTime;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES]; // Hide HUD if user is not interacting with buttons
self.hudDisplayed = NO; // Reset flag to make it work for the next segment
});
}
}
//edge case where segment end time is longer than the video
else if (sponsorSegment.endTime > self.currentVideoTotalMediaTime) {
[self isb_scrubToTime:self.currentVideoTotalMediaTime];
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
}
else {
[self isb_scrubToTime:sponsorSegment.endTime];
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
}
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 1) {
if (self.hud.superview != self.view && kShowSkipNotice) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
self.hud.mode = MBProgressHUDModeCustomView;
// Translate and add segment name to the skipped HUD (issue #70)
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
self.hud.label.text = [NSString stringWithFormat:LOC(@"SkippedSegment"), localizedSegment];
self.hud.label.numberOfLines = 0;
[self.hud.button setTitle:LOC(@"Unskip") forState:UIControlStateNormal];
[self.hud.button addTarget:self action:@selector(unskipSegment:) forControlEvents:UIControlEventTouchUpInside];
self.hud.offset = CGPointMake(self.view.frame.size.width, -MBProgressMaxOffset);
[self.hud hideAnimated:YES afterDelay:kSkipNoticeDuration];
// Play sound effect if option enabled
if (kSkipAudioNotification) {
playSponsorAudio();
}
}
}
if (self.currentSponsorSegment <= self.skipSegments.count-1 && [[kCategorySettings objectForKey:sponsorSegment.category] intValue] != 3) self.currentSponsorSegment ++;
}
else if (lroundf(arg2.time) > sponsorSegment.startTime && self.currentSponsorSegment != self.skipSegments.count && self.currentSponsorSegment != self.skipSegments.count-1) {
self.currentSponsorSegment ++;
}
else if (self.currentSponsorSegment == 0 && self.unskippedSegment != -1) {
self.currentSponsorSegment ++;
}
else if (self.currentSponsorSegment > 0 && lroundf(arg2.time) < self.skipSegments[self.currentSponsorSegment-1].startTime-0.01) {
if ([self isMDXActive]) {
}
else if (self.unskippedSegment != self.currentSponsorSegment-1) {
self.currentSponsorSegment--;
}
else if (arg2.time < self.skipSegments[self.currentSponsorSegment-1].startTime-0.01) {
self.unskippedSegment = -1;
}
}
}
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
[playerBarView maybeCreateMarkerViewsISB];
for (UIView *markerView in playerBarView.subviews) {
if (![playerBarView.sponsorMarkerViews containsObject:markerView] && playerBarView.skipSegments.count == 0) {
[playerBarView maybeCreateMarkerViewsISB];
return;
}
}
}
}
- (void)playbackController:(id)arg1 didActivateVideo:(id)arg2 withPlaybackData:(id)arg3 {
%orig;
if (self.isPlayingAd) return;
YTPlayerView *playerView = (YTPlayerView *)self.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
[MBProgressHUD hideHUDForView:playerView animated:YES]; //fix manual skip popup not disappearing when changing videos
self.hudDisplayed = NO; // Reset flag when changing videos
self.skipSegments = [NSMutableArray array];
self.userSkipSegments = [NSMutableArray array];
[SponsorBlockRequest getSponsorTimes:self.currentVideoID completionTarget:self completionSelector:@selector(setSkipSegments:) apiInstance:kAPIInstance];
self.currentSponsorSegment = 0;
self.unskippedSegment = -1;
overlayView.controlsOverlayView.playerViewController = self;
overlayView.controlsOverlayView.isDisplayingSponsorBlockViewController = NO;
YTSingleVideoController *activeVideo = self.activeVideo;
if ([activeVideo isKindOfClass:%c(YTSingleVideoController)]) {
if ([self.activeVideo.singleVideo respondsToSelector:@selector(video)]) self.channelID = self.activeVideo.singleVideo.video.videoDetails.channelId;
else self.channelID = self.activeVideo.singleVideo.playbackData.video.videoDetails.channelId;
}
}
}
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)arg1 {
%orig;
NSInteger totalSavedTime = 0;
for (SponsorSegment *segment in arg1) totalSavedTime += lroundf(segment.endTime) - lroundf(segment.startTime);
if (arg1.count > 0) {
NSInteger seconds = lroundf(self.currentVideoTotalMediaTime - totalSavedTime);
NSInteger hours = seconds / 3600;
NSInteger minutes = (seconds - (hours * 3600)) / 60;
seconds = seconds % 60;
if (hours >= 1) modifiedTimeString = [NSString stringWithFormat:@"%ld:%02ld:%02ld",hours, minutes, seconds];
else modifiedTimeString = [NSString stringWithFormat:@"%ld:%02ld", minutes, seconds];
}
else {
modifiedTimeString = nil;
}
}
%new
- (void)isb_fixVisualGlitch {
if (!self.isPlayingAd) {
YTPlayerView *playerView = (YTPlayerView *)self.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
[playerBarView maybeCreateMarkerViewsISB];
}
}
}
- (void)scrubToTime:(CGFloat)arg1 {
%orig;
[self isb_fixVisualGlitch];
}
- (void)seekToTime:(CGFloat)arg1 {
%orig;
[self isb_fixVisualGlitch];
}
%new
- (void)unskipSegment:(UIButton *)sender {
if (self.currentSponsorSegment > 0) {
[self isb_scrubToTime:self.skipSegments[self.currentSponsorSegment-1].startTime];
self.unskippedSegment = self.currentSponsorSegment-1;
} else {
[self isb_scrubToTime:self.skipSegments[self.currentSponsorSegment].startTime];
self.unskippedSegment = self.currentSponsorSegment;
}
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
%new
- (void)manuallySkipSegment:(UIButton *)sender {
SponsorSegment *sponsorSegment = [[SponsorSegment alloc] initWithStartTime:-1 endTime:-1 category:nil UUID:nil];
if (self.currentSponsorSegment <= self.skipSegments.count-1) {
sponsorSegment = self.skipSegments[self.currentSponsorSegment];
} else if (self.unskippedSegment != self.currentSponsorSegment-1) {
sponsorSegment = self.skipSegments[self.currentSponsorSegment-1];
}
if (sponsorSegment.endTime > self.currentVideoTotalMediaTime) {
[self isb_scrubToTime:self.currentVideoTotalMediaTime];
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
}
else {
[self isb_scrubToTime:sponsorSegment.endTime];
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
}
[MBProgressHUD hideHUDForView:self.view animated:YES];
// Prevent app crashing if segment was already skipped once
if (self.currentSponsorSegment < 0) {
self.currentSponsorSegment++;
}
// Reset flag immediately if segment was skipped
if (self.hudDisplayed != NO) {
self.hudDisplayed = NO;
}
// Play sound effect if option enabled
if (kSkipAudioNotification) {
playSponsorAudio();
}
}
%new
- (void)cancelHUD:(UIButton *)sender {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
- (void)setPlayerViewLayout:(NSInteger)arg1 {
%orig;
YTPlayerView *playerView = (YTPlayerView *)self.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
[playerBarView maybeCreateMarkerViewsISB];
}
}
- (void)updateViewportSizeProvider {
%orig;
YTPlayerView *playerView = (YTPlayerView *)self.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
[playerBarView maybeCreateMarkerViewsISB];
}
}
%end
%hook YTMainAppVideoPlayerOverlayViewController
- (void)updateTopRightButtonAvailability {
%orig;
YTMainAppVideoPlayerOverlayView *v = [self videoPlayerOverlayView];
YTMainAppControlsOverlayView *c = [v valueForKey:@"_controlsOverlayView"];
c.sponsorBlockButton.hidden = !kShowButtonsInPlayer;
c.sponsorStartedEndedButton.hidden = !kShowButtonsInPlayer || kHideStartEndButtonInPlayer;
[c setNeedsLayout];
}
%end
%hook YTMainAppControlsOverlayView
%property (retain, nonatomic) YTQTMButton *sponsorBlockButton;
%property (retain, nonatomic) YTQTMButton *sponsorStartedEndedButton;
%property (retain, nonatomic) YTPlayerViewController *playerViewController;
%property (nonatomic, assign) BOOL isDisplayingSponsorBlockViewController;
- (id)initWithDelegate:(id)delegate {
self = %orig;
if (kShowButtonsInPlayer) {
CGFloat padding = [[self class] topButtonAdditionalPadding];
self.sponsorBlockButton = [self buttonWithImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"PlayerInfoIconSponsorBlocker256px-20@2x" ofType:@"png"]] accessibilityLabel:@"iSponsorBlock" verticalContentPadding:padding];
[self.sponsorBlockButton addTarget:self action:@selector(sponsorBlockButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.sponsorBlockButton.hidden = YES;
self.sponsorBlockButton.alpha = 0;
if (!kHideStartEndButtonInPlayer) {
BOOL isStart = self.playerViewController.userSkipSegments.lastObject.endTime != -1;
NSString *startedEndedImagePath = isStart ? [tweakBundle pathForResource:@"sponsorblockstart-20@2x" ofType:@"png"] : [tweakBundle pathForResource:@"sponsorblockend-20@2x" ofType:@"png"];
self.sponsorStartedEndedButton = [self buttonWithImage:[UIImage imageWithContentsOfFile:startedEndedImagePath] accessibilityLabel:isStart ? @"iSponsorBlock start" : @"iSponsorBlock end" verticalContentPadding:padding];
[self.sponsorStartedEndedButton addTarget:self action:@selector(sponsorStartedEndedButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.sponsorStartedEndedButton.hidden = YES;
self.sponsorStartedEndedButton.alpha = 0;
}
@try {
UIView *containerView = [self valueForKey:@"_topControlsAccessibilityContainerView"];
[containerView addSubview:self.sponsorBlockButton];
if (!kHideStartEndButtonInPlayer) {
[containerView addSubview:self.sponsorStartedEndedButton];
}
} @catch (id ex) {
[self addSubview:self.sponsorBlockButton];
if (!kHideStartEndButtonInPlayer) {
[self addSubview:self.sponsorStartedEndedButton];
}
}
}
return self;
}
- (NSMutableArray *)topControls {
NSMutableArray <UIView *> *topControls = %orig;
if (kShowButtonsInPlayer) {
[topControls insertObject:self.sponsorBlockButton atIndex:0];
if (!kHideStartEndButtonInPlayer) {
[topControls insertObject:self.sponsorStartedEndedButton atIndex:0];
}
}
return topControls;
}
- (void)setTopOverlayVisible:(BOOL)visible isAutonavCanceledState:(BOOL)canceledState {
if (self.isDisplayingSponsorBlockViewController) {
%orig(NO, canceledState);
self.sponsorBlockButton.imageView.hidden = YES;
self.sponsorStartedEndedButton.imageView.hidden = YES;
return;
}
self.sponsorBlockButton.alpha = canceledState || !visible ? 0:1;
self.sponsorStartedEndedButton.alpha = canceledState || !visible ? 0:1;
%orig;
}
%new
- (void)sponsorBlockButtonPressed:(YTQTMButton *)sender {
self.isDisplayingSponsorBlockViewController = YES;
self.sponsorBlockButton.hidden = YES;
self.sponsorStartedEndedButton.hidden = YES;
if ([self.playerViewController playerViewLayout] == 3) [self.playerViewController didPressToggleFullscreen];
[self presentSponsorBlockViewController];
}
%new
- (void)sponsorStartedEndedButtonPressed:(YTQTMButton *)sender {
if (self.playerViewController.userSkipSegments.lastObject.endTime != -1) {
[self.playerViewController.userSkipSegments addObject:[[SponsorSegment alloc] initWithStartTime:self.playerViewController.currentVideoMediaTime endTime:-1 category:nil UUID:nil]];
[self.sponsorStartedEndedButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblockend-20@2x" ofType:@"png"]] forState:UIControlStateNormal];
}
else {
self.playerViewController.userSkipSegments.lastObject.endTime = self.playerViewController.currentVideoMediaTime;
if (self.playerViewController.userSkipSegments.lastObject.endTime != self.playerViewController.currentVideoMediaTime) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"End Time That You Set Was Less Than the Start Time, Please Select a Time After %ld:%02ld",lroundf(self.playerViewController.userSkipSegments.lastObject.startTime)/60, lroundf(self.playerViewController.userSkipSegments.lastObject.startTime)%60] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:alert animated:YES completion:nil];
return;
}
[self.sponsorStartedEndedButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblockstart-20@2x" ofType:@"png"]] forState:UIControlStateNormal];
}
}
%new
- (void)presentSponsorBlockViewController {
SponsorBlockViewController *addSponsorViewController = [[SponsorBlockViewController alloc] init];
addSponsorViewController.playerViewController = self.playerViewController;
addSponsorViewController.previousParentViewController = self.playerViewController.parentViewController;
addSponsorViewController.overlayView = self;
addSponsorViewController.preferredContentSize = CGSizeMake(CGRectGetWidth(self.playerViewController.view.frame), 0.9 * CGRectGetHeight(UIScreen.mainScreen.bounds));
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:addSponsorViewController animated:YES completion:nil];
self.isDisplayingSponsorBlockViewController = YES;
[self setOverlayVisible:NO];
}
%end
%hook YTInlinePlayerBarView
%property (strong, nonatomic) NSMutableArray *sponsorMarkerViews;
%property (strong, nonatomic) NSMutableArray *skipSegments;
%property (strong, nonatomic) YTPlayerViewController *playerViewController;
%new
- (void)maybeCreateMarkerViewsISB {
[self removeSponsorMarkers];
self.skipSegments = self.skipSegments;
}
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)arg1 {
%orig;
[self removeSponsorMarkers];
if ([kWhitelistedChannels containsObject:self.playerViewController.channelID]) {
return;
}
self.sponsorMarkerViews = [NSMutableArray array];
for (SponsorSegment *segment in arg1) {
CGFloat startTime = segment.startTime;
CGFloat endTime = segment.endTime;
CGFloat beginX = (startTime * self.frame.size.width) / self.totalTime;
CGFloat endX = (endTime * self.frame.size.width) / self.totalTime;
CGFloat markerWidth = MAX(endX - beginX, 0);
UIColor *color;
if ([segment.category isEqualToString:@"sponsor"]) color = colorWithHexString([kCategorySettings objectForKey:@"sponsorColor"]);
else if ([segment.category isEqualToString:@"intro"]) color = colorWithHexString([kCategorySettings objectForKey:@"introColor"]);
else if ([segment.category isEqualToString:@"outro"]) color = colorWithHexString([kCategorySettings objectForKey:@"outroColor"]);
else if ([segment.category isEqualToString:@"interaction"]) color = colorWithHexString([kCategorySettings objectForKey:@"interactionColor"]);
else if ([segment.category isEqualToString:@"selfpromo"]) color = colorWithHexString([kCategorySettings objectForKey:@"selfpromoColor"]);
else if ([segment.category isEqualToString:@"music_offtopic"]) color = colorWithHexString([kCategorySettings objectForKey:@"music_offtopicColor"]);
UIView *newMarkerView = [[UIView alloc] initWithFrame:CGRectZero];
newMarkerView.backgroundColor = color;
[self addSubview:newMarkerView];
newMarkerView.translatesAutoresizingMaskIntoConstraints = NO;
if (isnan(markerWidth) || !isfinite(beginX)) {
return;
}
[newMarkerView.widthAnchor constraintEqualToConstant:markerWidth].active = YES;
[newMarkerView.heightAnchor constraintEqualToConstant:2].active = YES;
[newMarkerView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:beginX].active = YES;
[newMarkerView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
[self.sponsorMarkerViews addObject:newMarkerView];
}
}
%new
- (void)removeSponsorMarkers {
for (UIView *markerView in self.sponsorMarkerViews) {
[markerView removeFromSuperview];
}
self.sponsorMarkerViews = [NSMutableArray array];
}
%end
%hook YTSegmentableInlinePlayerBarView
%property (strong, nonatomic) NSMutableArray *sponsorMarkerViews;
%property (strong, nonatomic) NSMutableArray *skipSegments;
%property (strong, nonatomic) YTPlayerViewController *playerViewController;
%new
- (void)maybeCreateMarkerViewsISB {
[self removeSponsorMarkers];
self.skipSegments = self.skipSegments;
}
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)arg1 {
%orig;
[self removeSponsorMarkers];
if ([kWhitelistedChannels containsObject:self.playerViewController.channelID]) {
return;
}
self.sponsorMarkerViews = [NSMutableArray array];
UIView *scrubber = [self valueForKey:@"_scrubberCircle"];
UIView *referenceView = [[self valueForKey:@"_segmentViews"] firstObject];
if (referenceView == nil) return;
CGFloat originY = referenceView.frame.origin.y;
for (SponsorSegment *segment in arg1) {
CGFloat startTime = segment.startTime;
CGFloat endTime = segment.endTime;
CGFloat beginX = (startTime * self.frame.size.width) / self.totalTime;
CGFloat endX = (endTime * self.frame.size.width) / self.totalTime;
CGFloat markerWidth = MAX(endX - beginX, 0);
UIColor *color;
if ([segment.category isEqualToString:@"sponsor"]) color = colorWithHexString([kCategorySettings objectForKey:@"sponsorColor"]);
else if ([segment.category isEqualToString:@"intro"]) color = colorWithHexString([kCategorySettings objectForKey:@"introColor"]);
else if ([segment.category isEqualToString:@"outro"]) color = colorWithHexString([kCategorySettings objectForKey:@"outroColor"]);
else if ([segment.category isEqualToString:@"interaction"]) color = colorWithHexString([kCategorySettings objectForKey:@"interactionColor"]);
else if ([segment.category isEqualToString:@"selfpromo"]) color = colorWithHexString([kCategorySettings objectForKey:@"selfpromoColor"]);
else if ([segment.category isEqualToString:@"music_offtopic"]) color = colorWithHexString([kCategorySettings objectForKey:@"music_offtopicColor"]);
if (isnan(markerWidth) || !isfinite(beginX)) {
return;
}
UIView *newMarkerView = [[UIView alloc] initWithFrame:CGRectMake(beginX, originY, markerWidth, 2)];
newMarkerView.userInteractionEnabled = NO;
newMarkerView.backgroundColor = color;
[self insertSubview:newMarkerView belowSubview:scrubber];
[self.sponsorMarkerViews addObject:newMarkerView];
}
}
%new
- (void)removeSponsorMarkers {
for (UIView *markerView in self.sponsorMarkerViews) {
[markerView removeFromSuperview];
}
self.sponsorMarkerViews = [NSMutableArray array];
}
%end
%hook YTInlinePlayerBarContainerView
- (instancetype)initWithScrubbedTimeLabelsDisplayBelowStoryboard:(BOOL)arg1 enableSegmentedProgressView:(BOOL)arg2 {
return %orig(arg1, YES);
}
//does the same thing as the method above on youtube v. 16.0x
- (instancetype)initWithEnableSegmentedProgressView:(BOOL)arg1 {
return %orig(YES);
}
- (BOOL)alwaysEnableSegmentedProgressView {
return YES;
}
- (void)setPeekableViewVisible:(BOOL)arg1 {
%orig;
if (kShowModifiedTime && modifiedTimeString && ![self.durationLabel.text containsString:modifiedTimeString]) {
NSString *text = [NSString stringWithFormat:@"%@ (%@)", self.durationLabel.text, modifiedTimeString];
self.durationLabel.text = text;
[self.durationLabel sizeToFit];
}
}
//thanks @iCraze >>
%new
- (id)playerBar {
return [self segmentablePlayerBar];
}
%end
%hook YTNGWatchLayerViewController
- (void)didCompleteFullscreenDismissAnimation {
%orig;
YTPlayerView *playerView = (YTPlayerView *)self.playerViewController.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if (!self.playerViewController.isPlayingAd && overlayView.controlsOverlayView.isDisplayingSponsorBlockViewController && [overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
[overlayView.controlsOverlayView presentSponsorBlockViewController];
}
}
%end
//For newer versions of YT the class name changed
%hook YTWatchLayerViewController
- (void)didCompleteFullscreenDismissAnimation {
%orig;
YTPlayerView *playerView = (YTPlayerView *)self.playerViewController.view;
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
if (!self.playerViewController.isPlayingAd && overlayView.controlsOverlayView.isDisplayingSponsorBlockViewController && [overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
[overlayView.controlsOverlayView presentSponsorBlockViewController];
}
}
%end
%hook YTPlayerView
//https://stackoverflow.com/questions/11770743/capturing-touches-on-a-subview-outside-the-frame-of-its-superview-using-hittest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.clipsToBounds || self.hidden || self.alpha == 0) {
return nil;
}
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result) return result;
}
return nil;
}
%end
%end
%group Cercube
//ew global variables
NSArray <SponsorSegment *> *skipSegments;
AVQueuePlayer *queuePlayer;
%hook CADownloadObject
+ (id)modelWithMetadata:(id)arg1 format:(id)arg2 context:(id)arg3 type:(id)arg4 audioOnly:(_Bool)arg5 directory:(id)arg6 {
CADownloadObject *downloadObject = %orig;
[SponsorBlockRequest getSponsorTimes:downloadObject.videoId completionTarget:downloadObject completionSelector:@selector(setSkipSegments:) apiInstance:kAPIInstance];
return downloadObject;
}
%new
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)skipSegments {
NSString *path = [self.filePath.stringByDeletingLastPathComponent stringByAppendingPathComponent:[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"plist"]];
[[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
NSMutableArray *segments = [NSMutableArray array];
for (SponsorSegment *segment in skipSegments) {
[segments addObject:@{
@"startTime" : @(segment.startTime),
@"endTime" : @(segment.endTime),
@"category" : segment.category,
@"UUID" : segment.UUID
}];
}
NSDictionary *dict = @{
@"skipSegments" : segments
};
[dict writeToURL:[NSURL fileURLWithPath:path isDirectory:NO] error:nil];
}
%end
%hook AVPlayerViewController
- (void)viewDidLoad {
%orig;
[(AVQueuePlayer *)self.player setPlayerViewController:self];
}
%end
%hook AVScrubber
//this is bad but i don't feel like finding a better way
- (void)layoutSubviews {
%orig;
[queuePlayer updateMarkerViews];
}
%end
%hook AVQueuePlayer
%property (strong, nonatomic) NSMutableArray *skipSegments;
%property (nonatomic, assign) NSInteger currentSponsorSegment;
%property (strong, nonatomic) MBProgressHUD *hud;
%property (nonatomic, assign) NSInteger unskippedSegment;
%property (nonatomic, assign) BOOL isSeeking;
%property (nonatomic, assign) NSInteger currentPlayerItem;
%property (strong, nonatomic) id timeObserver;
%property (strong, nonatomic) AVPlayerViewController *playerViewController;
%property (strong, nonatomic) NSMutableArray *markerViews;
%property (nonatomic, assign) BOOL hudDisplayed;
- (instancetype)initWithItems:(NSArray<AVPlayerItem *> *)items {
self.currentPlayerItem = 0;
queuePlayer = self;
return %orig;
}
- (void)seekToTime:(CMTime)time {
%orig;
self.isSeeking = YES;
[NSTimer scheduledTimerWithTimeInterval:1.0f repeats:NO block:^(NSTimer *timer) {
self.isSeeking = NO;
}];
}
- (void)_itemIsReadyToPlay:(id)arg1 {
%orig;
self.isSeeking = NO;
[self sponsorBlockSetup];
}
- (void)_advanceCurrentItemAccordingToFigPlaybackItem:(id)arg1 {
%orig;
if (self.currentPlayerItem + 1 < [self items].count) self.currentPlayerItem ++;
}
- (void)_removeItem:(id)arg1 {
%orig;
[self removeTimeObserver:self.timeObserver];
self.timeObserver = nil;
if (self.currentPlayerItem != 0) self.currentPlayerItem --;
[self sponsorBlockSetup];
}
%new
- (void)updateMarkerViews {
if (self.skipSegments.count > 0) {
CGFloat totalTime = [@([self items][self.currentPlayerItem].duration.value) floatValue] / [self items][self.currentPlayerItem].duration.timescale;
for (UIView *markerView in self.markerViews) {
AVScrubber *scrubber = self.playerViewController.contentView.playbackControlsView.scrubber;
CGFloat startTime = self.skipSegments[[self.markerViews indexOfObject:markerView]].startTime;
CGFloat endTime = self.skipSegments[[self.markerViews indexOfObject:markerView]].endTime;
CGFloat beginX = (startTime * scrubber.frame.size.width) / totalTime;
CGFloat endX = (endTime * scrubber.frame.size.width) / totalTime;
CGFloat markerWidth = endX - beginX;
markerView.frame = CGRectMake(beginX, scrubber.frame.size.height/2-2, markerWidth, 5);
}
}
}
%new
- (void)sponsorBlockSetup {
if ([self items].count <= 0) return;
NSString *path = [[[[self items][self.currentPlayerItem] _URL].path stringByDeletingPathExtension] stringByAppendingPathExtension:@"plist"];
NSDictionary *segmentsDict = [NSDictionary dictionaryWithContentsOfFile:path];
NSDictionary *segments = [segmentsDict objectForKey:@"skipSegments"];
self.skipSegments = [NSMutableArray array];
CGFloat totalTime = [@([self items][self.currentPlayerItem].duration.value) floatValue] / [self items][self.currentPlayerItem].duration.timescale;
for (UIView *markerView in self.markerViews) {
[markerView removeFromSuperview];
}
self.markerViews = [NSMutableArray array];
for (NSDictionary *dict in segments) {
SponsorSegment *segment = [[SponsorSegment alloc] initWithStartTime:[[dict objectForKey:@"startTime"] floatValue] endTime:[[dict objectForKey:@"endTime"] floatValue] category:[dict objectForKey:@"category"] UUID:[dict objectForKey:@"UUID"]];
[self.skipSegments addObject:segment];
AVScrubber *scrubber = self.playerViewController.contentView.playbackControlsView.scrubber;
CGFloat startTime = segment.startTime;
CGFloat endTime = segment.endTime;
CGFloat beginX = (startTime * scrubber.frame.size.width) / totalTime;
CGFloat endX = (endTime * scrubber.frame.size.width) / totalTime;
CGFloat markerWidth = endX - beginX;
UIView *markerView = [[UIView alloc] initWithFrame:CGRectMake(beginX, 2, markerWidth, 5)];
if ([segment.category isEqualToString:@"sponsor"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"sponsorColor"]);
else if ([segment.category isEqualToString:@"intro"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"introColor"]);
else if ([segment.category isEqualToString:@"outro"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"outroColor"]);
else if ([segment.category isEqualToString:@"interaction"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"interactionColor"]);
else if ([segment.category isEqualToString:@"selfpromo"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"selfpromoColor"]);
else if ([segment.category isEqualToString:@"music_offtopic"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"music_offtopicColor"]);
[scrubber addSubview:markerView];
[self.markerViews addObject:markerView];
}
skipSegments = self.skipSegments;
self.currentSponsorSegment = 0;
self.unskippedSegment = -1;
CMTime timeInterval = CMTimeMake(1,10);
__weak AVQueuePlayer *weakSelf = self;
[self removeTimeObserver:self.timeObserver];
self.timeObserver = nil;
self.timeObserver = [self addPeriodicTimeObserverForInterval:timeInterval queue:nil usingBlock:^(CMTime time) {
CGFloat timeFloat = [@(time.value) floatValue] / time.timescale;
if (weakSelf.skipSegments.count > 0) {
SponsorSegment *sponsorSegment = [[SponsorSegment alloc] initWithStartTime:-1 endTime:-1 category:nil UUID:nil];
if (weakSelf.currentSponsorSegment <= weakSelf.skipSegments.count-1) {
sponsorSegment = weakSelf.skipSegments[weakSelf.currentSponsorSegment];
}
else if (weakSelf.unskippedSegment != weakSelf.currentSponsorSegment-1) {
sponsorSegment = weakSelf.skipSegments[weakSelf.currentSponsorSegment-1];
}
if ((lroundf(timeFloat) == ceil(sponsorSegment.startTime) && timeFloat >= sponsorSegment.startTime) || (lroundf(timeFloat) >= ceil(sponsorSegment.startTime) && timeFloat < sponsorSegment.endTime)) {
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 3) {
if (weakSelf.hud.superview != weakSelf.playerViewController.view && weakSelf.hudDisplayed == NO) {
weakSelf.hud = [MBProgressHUD showHUDAddedTo:weakSelf.playerViewController.view animated:YES];
weakSelf.hudDisplayed = YES; // Set yes to make sure that HUD is not persistent (Issue #62)
weakSelf.hud.mode = MBProgressHUDModeCustomView;
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
NSString *localizedManualSkip = LOC(@"ManuallySkipReminder");
NSString *formattedManualSkip = [NSString stringWithFormat:localizedManualSkip, localizedSegment, lroundf(sponsorSegment.startTime)/60, lroundf(sponsorSegment.startTime)%60, lroundf(sponsorSegment.endTime)/60, lroundf(sponsorSegment.endTime)%60];
weakSelf.hud.label.text = formattedManualSkip;
weakSelf.hud.label.numberOfLines = 0;
[weakSelf.hud.button setTitle:LOC(@"Skip") forState:UIControlStateNormal];
[weakSelf.hud.button addTarget:weakSelf action:@selector(manuallySkipSegment:) forControlEvents:UIControlEventTouchUpInside];
// Add custom button to hide HUD
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
UIImage *cancelImage = [[UIImage systemImageNamed:@"x.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[cancelButton setImage:cancelImage forState:UIControlStateNormal];
[cancelButton setTintColor:[[UIColor blackColor] colorWithAlphaComponent:0.7]];
[cancelButton addTarget:weakSelf action:@selector(cancelHUD:) forControlEvents:UIControlEventTouchUpInside];
UIView *buttonSuperview = weakSelf.hud.button.superview;
[buttonSuperview addSubview:cancelButton];
CGFloat buttonSpacing = 10.0;
cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[cancelButton.topAnchor constraintEqualToAnchor:weakSelf.hud.button.topAnchor],
[cancelButton.leadingAnchor constraintEqualToAnchor:weakSelf.hud.button.trailingAnchor constant:buttonSpacing],
[cancelButton.heightAnchor constraintEqualToAnchor:weakSelf.hud.button.heightAnchor]
]];
weakSelf.hud.offset = CGPointMake(weakSelf.playerViewController.view.frame.size.width, -MBProgressMaxOffset);
double delayInSeconds = sponsorSegment.endTime - sponsorSegment.startTime;
// Use a delay equal to the length of the sponsored segment to avoid HUD call
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:weakSelf.playerViewController.view animated:YES]; // Hide HUD if user is not interacting with buttons
weakSelf.hudDisplayed = NO; // Reset flag to make it work for the next segment
});
}
}
else if (sponsorSegment.endTime > totalTime) {
[weakSelf seekToTime:CMTimeMake(totalTime,1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}
else {
[weakSelf seekToTime:CMTimeMake(sponsorSegment.endTime,1)];
}
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 1) {
if (weakSelf.hud.superview != weakSelf.playerViewController.view && kShowSkipNotice) {
[MBProgressHUD hideHUDForView:weakSelf.playerViewController.view animated:YES];
weakSelf.hud = [MBProgressHUD showHUDAddedTo:weakSelf.playerViewController.view animated:YES];
weakSelf.hud.mode = MBProgressHUDModeCustomView;
// Translate and add segment name to the skipped HUD (issue #70)
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
weakSelf.hud.label.text = [NSString stringWithFormat:LOC(@"SkippedSegment"), localizedSegment];
weakSelf.hud.label.numberOfLines = 0;
[weakSelf.hud.button setTitle:LOC(@"Unskip") forState:UIControlStateNormal];
[weakSelf.hud.button addTarget:weakSelf action:@selector(unskipSegment:) forControlEvents:UIControlEventTouchUpInside];
weakSelf.hud.offset = CGPointMake(weakSelf.playerViewController.view.frame.size.width, -MBProgressMaxOffset);
[weakSelf.hud hideAnimated:YES afterDelay:kSkipNoticeDuration];
// Play sound effect if option enabled
if (kSkipAudioNotification) {
playSponsorAudio();
}
}
}
if (weakSelf.currentSponsorSegment <= weakSelf.skipSegments.count-1) weakSelf.currentSponsorSegment ++;
}
else if (lroundf(timeFloat) > sponsorSegment.startTime && weakSelf.currentSponsorSegment < weakSelf.skipSegments.count-1) {
weakSelf.currentSponsorSegment ++;
}
else if (weakSelf.currentSponsorSegment == 0 && weakSelf.unskippedSegment != -1) {
weakSelf.currentSponsorSegment ++;
}
else if (weakSelf.currentSponsorSegment > 0 && lroundf(timeFloat) < weakSelf.skipSegments[weakSelf.currentSponsorSegment-1].startTime-0.01) {
if (weakSelf.unskippedSegment != weakSelf.currentSponsorSegment-1) {
weakSelf.currentSponsorSegment--;
}
else if (timeFloat < weakSelf.skipSegments[weakSelf.currentSponsorSegment-1].startTime-0.01) {
weakSelf.unskippedSegment = -1;
}
}
}
}];
}
%new
- (void)unskipSegment:(UIButton *)sender {
if (self.currentSponsorSegment > 0) {
[self seekToTime:CMTimeMake(self.skipSegments[self.currentSponsorSegment-1].startTime,1)];
self.unskippedSegment = self.currentSponsorSegment-1;
} else {
[self seekToTime:CMTimeMake(self.skipSegments[self.currentSponsorSegment].startTime,1)];
self.unskippedSegment = self.currentSponsorSegment;
}
[MBProgressHUD hideHUDForView:self.playerViewController.view animated:YES];
}
%end
%end
%group JustSettings
NSInteger pageStyle = 0;
%hook YTRightNavigationButtons
%property (retain, nonatomic) YTQTMButton *sponsorBlockButton;
- (NSMutableArray *)buttons {
NSMutableArray *retVal = %orig.mutableCopy;
[self.sponsorBlockButton removeFromSuperview];
[self addSubview:self.sponsorBlockButton];
if (!self.sponsorBlockButton || pageStyle != [%c(YTPageStyleController) pageStyle]) {
self.sponsorBlockButton = [%c(YTQTMButton) iconButton];
self.sponsorBlockButton.frame = CGRectMake(0, 0, 40, 40);
if ([%c(YTPageStyleController) pageStyle]) { //dark mode
[self.sponsorBlockButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblocksettings-20@2x" ofType:@"png"]] forState:UIControlStateNormal];
}
else { //light mode
UIImage *image = [UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblocksettings-20@2x" ofType:@"png"]];
image = [image imageWithTintColor:UIColor.blackColor renderingMode:UIImageRenderingModeAlwaysTemplate];
[self.sponsorBlockButton setImage:image forState:UIControlStateNormal];
[self.sponsorBlockButton setTintColor:UIColor.blackColor];
}
pageStyle = [%c(YTPageStyleController) pageStyle];
[self.sponsorBlockButton addTarget:self action:@selector(sponsorBlockButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[retVal insertObject:self.sponsorBlockButton atIndex:0];
}
return retVal;
}
- (NSMutableArray *)visibleButtons {
NSMutableArray *retVal = %orig.mutableCopy;
//fixes button overlapping yt logo on smaller devices
[self setLeadingPadding:-10];
if (self.sponsorBlockButton) {
[self.sponsorBlockButton removeFromSuperview];
[self addSubview:self.sponsorBlockButton];
[retVal insertObject:self.sponsorBlockButton atIndex:0];
}
return retVal;
}
%new
- (void)sponsorBlockButtonPressed:(UIButton *)sender {
SponsorBlockSettingsController *settingsController = [[SponsorBlockSettingsController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsController];
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:navigationController animated:YES completion:nil];
}
%end
%end
static void loadPrefs() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"iSponsorBlock.plist"];
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
[settings addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:path]];
kIsEnabled = [settings objectForKey:@"enabled"] ? [[settings objectForKey:@"enabled"] boolValue] : YES;
kUserID = [settings objectForKey:@"userID"] ? [settings objectForKey:@"userID"] : [[NSUUID UUID] UUIDString];
// reset to uuid if user set to an empty string
if ([kUserID isEqualToString:@""]) kUserID = [[NSUUID UUID] UUIDString];
kAPIInstance = [settings objectForKey:@"apiInstance"] ? [settings objectForKey:@"apiInstance"] : @"https://sponsor.ajay.app/api";
// reset to official if user set to an empty string
if ([kAPIInstance isEqualToString:@""]) kAPIInstance = @"https://sponsor.ajay.app/api";
kCategorySettings = [settings objectForKey:@"categorySettings"] ? [settings objectForKey:@"categorySettings"] : @{
@"sponsor" : @1,
@"sponsorColor" : hexFromUIColor(UIColor.greenColor),
@"intro" : @0,
@"introColor" : hexFromUIColor(UIColor.systemTealColor),
@"outro" : @0,
@"outroColor" : hexFromUIColor(UIColor.blueColor),
@"interaction" : @0,
@"interactionColor" : hexFromUIColor(UIColor.systemPinkColor),
@"selfpromo" : @0,
@"selfpromoColor" : hexFromUIColor(UIColor.yellowColor),
@"music_offtopic" : @0,
@"music_offtopicColor" : hexFromUIColor(UIColor.orangeColor)
};
kMinimumDuration = [settings objectForKey:@"minimumDuration"] ? [[settings objectForKey:@"minimumDuration"] floatValue] : 0.0f;
kShowSkipNotice = [settings objectForKey:@"showSkipNotice"] ? [[settings objectForKey:@"showSkipNotice"] boolValue] : YES;
kShowButtonsInPlayer = [settings objectForKey:@"showButtonsInPlayer"] ? [[settings objectForKey:@"showButtonsInPlayer"] boolValue] : YES;
kHideStartEndButtonInPlayer = [settings objectForKey:@"hideStartEndButtonInPlayer"] ? [[settings objectForKey:@"hideStartEndButtonInPlayer"] boolValue] : NO;
kShowModifiedTime = [settings objectForKey:@"showModifiedTime"] ? [[settings objectForKey:@"showModifiedTime"] boolValue] : YES;
kSkipAudioNotification = [settings objectForKey:@"skipAudioNotification"] ? [[settings objectForKey:@"skipAudioNotification"] boolValue] : NO;
kEnableSkipCountTracking = [settings objectForKey:@"enableSkipCountTracking"] ? [[settings objectForKey:@"enableSkipCountTracking"] boolValue] : YES;
kSkipNoticeDuration = [settings objectForKey:@"skipNoticeDuration"] ? [[settings objectForKey:@"skipNoticeDuration"] floatValue] : 3.0f;
kWhitelistedChannels = [settings objectForKey:@"whitelistedChannels"] ? [(NSArray *)[settings objectForKey:@"whitelistedChannels"] mutableCopy] : [NSMutableArray array];
NSDictionary *newSettings = @{
@"enabled" : @(kIsEnabled),
@"userID" : kUserID,
@"apiInstance" : kAPIInstance,
@"categorySettings" : kCategorySettings,
@"minimumDuration" : @(kMinimumDuration),
@"showSkipNotice" : @(kShowSkipNotice),
@"showButtonsInPlayer" : @(kShowButtonsInPlayer),
@"hideStartEndButtonInPlayer" : @(kHideStartEndButtonInPlayer),
@"showModifiedTime" : @(kShowModifiedTime),
@"skipAudioNotification" : @(kSkipAudioNotification),
@"enableSkipCountTracking" : @(kEnableSkipCountTracking),
@"skipNoticeDuration" : @(kSkipNoticeDuration),
@"whitelistedChannels" : kWhitelistedChannels
};
if (![newSettings isEqualToDictionary:settings]) {
[newSettings writeToURL:[NSURL fileURLWithPath:path isDirectory:NO] error:nil];
}
}
%group LateLoad
%hook YTAppDelegate
- (BOOL)application:(id)arg1 didFinishLaunchingWithOptions:(id)arg2 {
BOOL orig = %orig;
loadPrefs();
if (kIsEnabled) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if (dlopen(ROOT_PATH("/Library/MobileSubstrate/DynamicLibraries/Cercube.dylib"), RTLD_LAZY)) {
%init(Cercube)
NSString *downloadsDirectory = [documentsDirectory stringByAppendingPathComponent:@"Carida_Files"];
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:downloadsDirectory error:nil];
for (NSString *path in files) {
if ([path.pathExtension isEqualToString:@"plist"]) {
NSString *mp4Path = [downloadsDirectory stringByAppendingPathComponent:[[path stringByDeletingPathExtension] stringByAppendingPathExtension:@"mp4"]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:mp4Path];
if (!fileExists) {
[[NSFileManager defaultManager] removeItemAtPath:[downloadsDirectory stringByAppendingPathComponent:path] error:nil];
}
}
}
}
%init(Main);
}
return orig;
}
%end
%end
static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
loadPrefs();
}
%ctor {
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.galacticdev.isponsorblockprefs.changed"), NULL, CFNotificationSuspensionBehaviorCoalesce);
%init(LateLoad);
%init(JustSettings);
}
%dtor {
if (dlopen(ROOT_PATH("/Library/MobileSubstrate/DynamicLibraries/Cercube.dylib"), RTLD_LAZY)) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *downloadsDirectory = [documentsDirectory stringByAppendingPathComponent:@"Carida_Files"];
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:downloadsDirectory error:nil];
for (NSString *path in files) {
if ([path.pathExtension isEqualToString:@"plist"]) {
NSString *mp4Path = [downloadsDirectory stringByAppendingPathComponent:[[path stringByDeletingPathExtension] stringByAppendingPathExtension:@"mp4"]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:mp4Path];
if (!fileExists) {
[[NSFileManager defaultManager] removeItemAtPath:[downloadsDirectory stringByAppendingPathComponent:path] error:nil];
}
}
}
}
}