From 20fbe88bc794adbb1625325825dc07bf7bcf5b73 Mon Sep 17 00:00:00 2001 From: lint <47455468+lint@users.noreply.github.com> Date: Fri, 27 Dec 2019 18:44:29 -0500 Subject: [PATCH] Add Beam support --- prefs/Resources/Root.plist | 14 ++ tweak/Beam.h | 56 ++++++++ tweak/Beam.xm | 277 +++++++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 tweak/Beam.h create mode 100644 tweak/Beam.xm diff --git a/prefs/Resources/Root.plist b/prefs/Resources/Root.plist index 1e6f384..45ab025 100644 --- a/prefs/Resources/Root.plist +++ b/prefs/Resources/Root.plist @@ -110,6 +110,20 @@ label Enable Antenna + + PostNotification + com.lint.undelete.prefs.changed + cell + PSSwitchCell + default + + defaults + com.lint.undelete.prefs + key + isBeamEnabled + label + Enable Beam + cell PSGroupCell diff --git a/tweak/Beam.h b/tweak/Beam.h new file mode 100644 index 0000000..dd9551b --- /dev/null +++ b/tweak/Beam.h @@ -0,0 +1,56 @@ + +/* -- Comment Interfaces -- */ + +@interface Comment +@property(strong, nonatomic) NSString *author; +@property(strong, nonatomic) NSString *content; +@property(strong, nonatomic) NSString *identifier; +@property(strong, nonatomic) id markdownString; +@end + +@interface CommentCell : UIView +@property(strong, nonatomic) id comment; +@property(strong, nonatomic) id commentContentView; +@property(strong, nonatomic) id authorButton; +@property(assign, nonatomic) BOOL isCollapsed; +@property(assign, nonatomic) BOOL commentDidChange; +-(void) reloadContents; + +//custom elements +@property(strong, nonatomic) UIButton *undeleteButton; +@end + +/* -- Post Interfaces -- */ + +@interface Post +@property(strong, nonatomic) NSString *author; +@property(strong, nonatomic) NSString *content; +@property(strong, nonatomic) NSString *identifier; +@property(strong, nonatomic) id markdownString; +@property(strong, nonatomic) NSNumber *isSelfText; +@end + +@interface PostDetailEmbeddedViewController +@property(strong, nonatomic) UITableView *tableView; +@property(strong, nonatomic) NSArray *content; + +//custom elements +@property(strong, nonatomic) id selfTextView; +@property(strong, nonatomic) id metadataView; +@property(strong, nonatomic) id post; +@end + +@interface PostToolbarView : UIView +@property(strong, nonatomic) id post; + +//custom elements +@property(strong, nonatomic) UIButton *undeleteButton; +@end + +@interface PostSelfTextPartCell +-(id) _viewControllerForAncestor; +@end + +@interface PostMetadataView +@property(strong, nonatomic) id post; +@end diff --git a/tweak/Beam.xm b/tweak/Beam.xm new file mode 100644 index 0000000..42475d3 --- /dev/null +++ b/tweak/Beam.xm @@ -0,0 +1,277 @@ + +#import "Beam.h" + +static BOOL isBeamEnabled; +static BOOL isTFDeletedOnly; +static CGFloat pushshiftRequestTimeoutValue; + +%group Beam + +%hook CommentCell +%property(strong, nonatomic) UIButton *undeleteButton; + +-(void) layoutSubviews{ + %orig; + + UIButton *undeleteButton = [self undeleteButton]; + + if (undeleteButton){ + if ([self isCollapsed]){ + [undeleteButton setHidden:YES]; + } else { + [undeleteButton setHidden:NO]; + } + } else { + + NSString *commentBody = [[self comment] content]; + + HBLogDebug(@"body: %@", commentBody); + + if ((isTFDeletedOnly && ([commentBody isEqualToString:@"[deleted]"] || [commentBody isEqualToString:@"[removed]"])) || !isTFDeletedOnly){ + + CGFloat authorTextHeight = [[self authorButton] frame].size.height; + + undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [undeleteButton addTarget:self action:@selector(handleUndeleteCommentAction:) forControlEvents:UIControlEventTouchUpInside]; + + [undeleteButton setImage:[UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"] forState:UIControlStateNormal]; + undeleteButton.imageView.contentMode = UIViewContentModeScaleAspectFit; + undeleteButton.imageEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); + + undeleteButton.frame = CGRectMake([[UIScreen mainScreen] bounds].size.width - authorTextHeight - 5, 5, authorTextHeight, authorTextHeight); + + [[self commentContentView] addSubview:undeleteButton]; + [self setUndeleteButton:undeleteButton]; + } + } +} + +%new +-(void) handleUndeleteCommentAction:(id) sender{ + + [sender setEnabled:NO]; + + id comment = [self comment]; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + + [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/comment/?ids=%@&fields=author,body", [comment identifier]]]]; + [request setHTTPMethod:@"GET"]; + [request setTimeoutInterval:pushshiftRequestTimeoutValue]; + + [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + + NSString *author = @"[author]"; + NSString *body = @"[body]"; + + if (data != nil && error == nil){ + id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if ([[jsonData objectForKey:@"data"] count] != 0){ + author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; + body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"]; + if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ + body = @"[pushshift was unable to archive this]"; + } + } else { + body = @"[pushshift has not archived this yet]"; + } + } else if (error != nil || data == nil){ + body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]]; + } + + [comment setAuthor:author]; + [comment setContent:body]; + [comment setMarkdownString:nil]; + + [self setCommentDidChange:YES]; + [self performSelectorOnMainThread:@selector(reloadContents) withObject:nil waitUntilDone:YES]; + [[MSHookIvar(self, "delegate") tableView] performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; + + [sender setEnabled:YES]; + }]; +} + +%end + + +%hook PostSelfTextPartCell + +-(void) setSelected:(BOOL) arg1 animated:(BOOL) arg2{ + %orig; + + id postViewController = [self _viewControllerForAncestor]; + + if ([postViewController isMemberOfClass:objc_getClass("beam.PostDetailEmbeddedViewController")]){ + + [postViewController setSelfTextView:self]; + [postViewController setPost:[self post]]; + } +} + +%end + + +%hook PostMetadataView + +-(void) layoutSubviews{ + %orig; + + id postViewController = MSHookIvar(self, "delegate"); + + if ([postViewController isMemberOfClass:objc_getClass("beam.PostDetailEmbeddedViewController")]){ + [postViewController setMetadataView:self]; + } +} + +%end + + +%hook PostToolbarView +%property(strong, nonatomic) UIButton *undeleteButton; + +-(void) layoutSubviews{ + %orig; + + if (![self undeleteButton] && [[[self post] isSelfText] boolValue] && [MSHookIvar(self, "delegate") isMemberOfClass:objc_getClass("beam.PostDetailEmbeddedViewController")]){ + + NSString *postBody = [[self post] content]; + + if ((isTFDeletedOnly && ([postBody isEqualToString:@"[deleted]"] || [postBody isEqualToString:@"[removed]"])) || !isTFDeletedOnly){ + + id moreButton = MSHookIvar(self, "moreButton"); + + CGFloat buttonHeight = [moreButton frame].size.height; + + UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [undeleteButton addTarget:MSHookIvar(self, "delegate") action:@selector(handleUndeletePostAction:) forControlEvents:UIControlEventTouchUpInside]; + + [undeleteButton setImage:[UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"] forState:UIControlStateNormal]; + undeleteButton.imageView.contentMode = UIViewContentModeScaleAspectFit; + undeleteButton.imageEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); + + undeleteButton.frame = CGRectMake([moreButton frame].origin.x - buttonHeight, 1, buttonHeight, buttonHeight); + + [self addSubview:undeleteButton]; + [self setUndeleteButton:undeleteButton]; + } + } +} + +%end + +%hook PostDetailEmbeddedViewController +%property(strong, nonatomic) id selfTextView; +%property(strong, nonatomic) id metadataView; +%property(strong, nonatomic) id post; + +%new +-(void) handleUndeletePostAction:(id) sender{ + + [sender setEnabled:NO]; + + id post = [self post]; + + if (post){ + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + + [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/submission/?ids=%@&fields=author,selftext", [post identifier]]]]; + [request setHTTPMethod:@"GET"]; + [request setTimeoutInterval:pushshiftRequestTimeoutValue]; + + [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + + NSString *author = @"[author]"; + NSString *body = @"[body]"; + + if (data != nil && error == nil){ + id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if ([[jsonData objectForKey:@"data"] count] != 0){ + author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; + body = [[jsonData objectForKey:@"data"][0] objectForKey:@"selftext"]; + if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ + body = @"[pushshift was unable to archive this]"; + } + } else { + body = @"[pushshift has not archived this yet]"; + } + } else if (error != nil || data == nil){ + body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]]; + } + + [post setAuthor:author]; + [post setContent:body]; + [post setMarkdownString:nil]; + + if ([self selfTextView]){ + [[self selfTextView] performSelectorOnMainThread:@selector(reloadContents) withObject:nil waitUntilDone:YES]; + } + + if ([self metadataView]){ + [[self metadataView] performSelectorOnMainThread:@selector(setPost:) withObject:post waitUntilDone:YES]; + } + + [[self tableView] performSelectorOnMainThread:@selector(reloadData) withObject:post waitUntilDone:NO]; + + [sender setEnabled:YES]; + }]; + } +} + +%end + +%end + + +static void loadPrefs(){ + NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"]; + + if (prefs){ + + if ([prefs objectForKey:@"isBeamEnabled"] != nil){ + isBeamEnabled = [[prefs objectForKey:@"isBeamEnabled"] boolValue]; + } else { + isBeamEnabled = YES; + } + + if ([prefs objectForKey:@"isTFDeletedOnly"] != nil) { + isTFDeletedOnly = [[prefs objectForKey:@"isTFDeletedOnly"] boolValue]; + } else { + isTFDeletedOnly = YES; + } + + if ([prefs objectForKey:@"requestTimeoutValue"] != nil){ + pushshiftRequestTimeoutValue = [[prefs objectForKey:@"requestTimeoutValue"] doubleValue]; + } else { + pushshiftRequestTimeoutValue = 10; + } + + } else { + isBeamEnabled = YES; + isTFDeletedOnly = YES; + pushshiftRequestTimeoutValue = 10; + } +} + +static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { + loadPrefs(); +} + + +%ctor{ + loadPrefs(); + + NSString* processName = [[NSProcessInfo processInfo] processName]; + + if ([processName isEqualToString:@"beam"]){ + if (isBeamEnabled){ + + CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); + + %init(Beam, CommentCell = objc_getClass("beam.CommentCell"), PostDetailEmbeddedViewController = objc_getClass("beam.PostDetailEmbeddedViewController"), PostToolbarView = objc_getClass("beam.PostToolbarView"), PostSelfTextPartCell = objc_getClass("beam.PostSelfTextPartCell"), PostMetadataView = objc_getClass("beam.PostMetadataView")); + } + } +} +