You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
13KB

  1. #import "Apollo.h"
  2. #import "assets/TFHelper.h"
  3. static BOOL isEnabled;
  4. static BOOL isApolloEnabled;
  5. static BOOL isTFDeletedOnly;
  6. static CGFloat pushshiftRequestTimeoutValue;
  7. static BOOL shouldApolloHaveButton;
  8. %group Apollo
  9. NSDictionary* apolloBodyAttributes = nil;
  10. BOOL shouldAddUndeleteCell = NO;
  11. id apolloCommentCell;
  12. id apolloCommentController;
  13. %hook ApolloButtonNode
  14. %end
  15. %hook IconActionTableViewCell
  16. %end
  17. %hook RKComment
  18. - (BOOL)isDeleted {
  19. return NO;
  20. }
  21. - (BOOL)isModeratorRemoved {
  22. return NO;
  23. }
  24. %end
  25. //1.8+, unsure why this is all I needed to add for 1.8 support, the rest of this still works even w/o changing RKComment and RKLink references
  26. %hook RDKComment
  27. - (BOOL)isDeleted {
  28. return NO;
  29. }
  30. - (BOOL)isModeratorRemoved {
  31. return NO;
  32. }
  33. %end
  34. %hook RKLink
  35. %property(strong, nonatomic) NSString *undeleteAuthor;
  36. - (id)author {
  37. return [self undeleteAuthor] ? [self undeleteAuthor] : %orig;
  38. }
  39. %end
  40. %hook RDKLink
  41. %property(strong, nonatomic) NSString *undeleteAuthor;
  42. - (id)author {
  43. return [self undeleteAuthor] ? [self undeleteAuthor] : %orig;
  44. }
  45. %end
  46. %hook MarkdownRenderer
  47. + (id)attributedStringFromHTML:(id)arg1 attributes:(id)arg2 compact:(BOOL)arg3 {
  48. apolloBodyAttributes = [arg2 copy];
  49. return %orig;
  50. }
  51. %end
  52. %hook ActionController
  53. - (id) tableView:(id)arg1 cellForRowAtIndexPath:(NSIndexPath *)arg2 {
  54. if (shouldAddUndeleteCell) {
  55. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1){
  56. id undeleteCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell" forIndexPath:arg2];
  57. id prevCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell"];
  58. UIImageView *prevCellImageView = MSHookIvar<UIImageView *>(prevCell, "iconImageView");
  59. CGSize prevImageSize = [[prevCellImageView image] size];
  60. UIImage *undeleteImage;
  61. //if (@available(iOS 13.0, *)){
  62. if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"13.0")) {
  63. undeleteImage = [UIImage systemImageNamed:@"eye"];
  64. if (!undeleteImage){
  65. undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  66. } else {
  67. CGSize squareSize = CGSizeMake(undeleteImage.size.width, undeleteImage.size.width);
  68. UIGraphicsBeginImageContextWithOptions(squareSize, NO, 0);
  69. [undeleteImage drawInRect:CGRectMake(0, (squareSize.height - undeleteImage.size.height) / 2, squareSize.width, undeleteImage.size.height)];
  70. undeleteImage = UIGraphicsGetImageFromCurrentImageContext();
  71. UIGraphicsEndImageContext();
  72. }
  73. } else {
  74. undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  75. }
  76. CGFloat undeleteImageSizeValue = prevImageSize.width > prevImageSize.height ? prevImageSize.width : prevImageSize.height;
  77. if (undeleteImageSizeValue == 0) {
  78. undeleteImageSizeValue = 25;
  79. }
  80. UIGraphicsBeginImageContextWithOptions(CGSizeMake(undeleteImageSizeValue, undeleteImageSizeValue), NO, 0);
  81. [undeleteImage drawInRect:CGRectMake(0, 0, undeleteImageSizeValue, undeleteImageSizeValue)];
  82. undeleteImage = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  83. UIGraphicsEndImageContext();
  84. UILabel *undeleteLabel = MSHookIvar<UILabel *>(undeleteCell, "actionTitleLabel");
  85. UIImageView *undeleteImageView = MSHookIvar<UIImageView *>(undeleteCell, "iconImageView");
  86. undeleteLabel.text = @"TF Did That Say?";
  87. undeleteImageView.image = undeleteImage;
  88. return undeleteCell;
  89. }
  90. }
  91. return %orig;
  92. }
  93. - (void)tableView:(id)arg1 didSelectRowAtIndexPath:(NSIndexPath *)arg2 {
  94. if (shouldAddUndeleteCell) {
  95. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1){
  96. if (apolloCommentCell) {
  97. [apolloCommentCell undeleteCellWasSelected];
  98. } else {
  99. [apolloCommentController undeleteCellWasSelected];
  100. }
  101. }
  102. }
  103. %orig;
  104. }
  105. - (NSInteger) tableView:(id)arg1 numberOfRowsInSection:(NSInteger)arg2 {
  106. if (shouldAddUndeleteCell){
  107. return %orig + 1;
  108. } else {
  109. return %orig;
  110. }
  111. }
  112. - (id) animationControllerForDismissedController:(id)arg1 {
  113. shouldAddUndeleteCell = NO;
  114. return %orig;
  115. }
  116. %end
  117. %hook CommentCellNode
  118. %property(strong,nonatomic) UIButton *undeleteButton;
  119. - (void)moreOptionsTappedWithSender:(id)arg1 {
  120. if (!shouldApolloHaveButton){
  121. NSString *commentBody = [MSHookIvar<RKComment *>(self, "comment") body];
  122. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:commentBody isDeletedOnly:isTFDeletedOnly]){
  123. shouldAddUndeleteCell = YES;
  124. apolloCommentCell = self;
  125. apolloCommentController = nil;
  126. }
  127. }
  128. %orig;
  129. }
  130. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  131. if (!shouldApolloHaveButton){
  132. NSString *commentBody = [MSHookIvar<RKComment *>(self, "comment") body];
  133. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:commentBody isDeletedOnly:isTFDeletedOnly]){
  134. shouldAddUndeleteCell = YES;
  135. apolloCommentCell = self;
  136. apolloCommentController = nil;
  137. }
  138. }
  139. %orig;
  140. }
  141. - (void)didLoad {
  142. %orig;
  143. if (shouldApolloHaveButton){
  144. NSString *commentBody = [MSHookIvar<RKComment *>(self, "comment") body];
  145. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:commentBody isDeletedOnly:isTFDeletedOnly]){
  146. CGFloat imageSize = 20.0f;
  147. UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  148. [undeleteButton addTarget:self action:@selector(didTapUndeleteButton:) forControlEvents:UIControlEventTouchUpInside];
  149. undeleteButton.frame = CGRectMake(0, 0, imageSize, imageSize);
  150. UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  151. [undeleteButton setImage:undeleteImage forState:UIControlStateNormal];
  152. [[self view] addSubview:undeleteButton];
  153. [self setUndeleteButton:undeleteButton];
  154. }
  155. }
  156. }
  157. - (void)_layoutSublayouts {
  158. %orig;
  159. if (shouldApolloHaveButton){
  160. if ([self undeleteButton]){
  161. CGFloat imageSize = 20.0f;
  162. id moreNode = MSHookIvar<id>(self, "moreOptionsNode");
  163. id ageNode = MSHookIvar<id>(self, "ageNode");
  164. CGRect nodeFrame = [moreNode frame];
  165. CGFloat centerHeight = (nodeFrame.size.height + nodeFrame.origin.y * 2) / 2.0f;
  166. CGFloat nodeSpacing = [ageNode frame].origin.x - nodeFrame.origin.x - nodeFrame.size.width;
  167. [[self undeleteButton] setFrame:CGRectMake(nodeFrame.origin.x - imageSize - nodeSpacing, centerHeight - (imageSize / 2), imageSize, imageSize)];
  168. }
  169. }
  170. }
  171. %new
  172. - (void)didTapUndeleteButton:(id)sender {
  173. [sender setEnabled:NO];
  174. id comment = MSHookIvar<id>(self, "comment");
  175. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:@{@"sender" : sender} completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  176. }
  177. %new
  178. - (void)undeleteCellWasSelected {
  179. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  180. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  181. }
  182. %new
  183. - (void)completeUndeleteCommentAction:(NSDictionary *)data {
  184. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  185. id bodyNode = MSHookIvar<id>(self, "bodyNode");
  186. id authorNode = MSHookIvar<id>(self, "authorNode");
  187. NSString *author = data[@"author"];
  188. NSString *body = data[@"body"];
  189. [comment setAuthor:author];
  190. [comment setBody:body];
  191. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  192. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  193. NSAttributedString *newAuthorAttributedString = [[NSAttributedString alloc] initWithString:author attributes:authorStringAttributes];
  194. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  195. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  196. if ([data objectForKey:@"sender"]) {
  197. [data[@"sender"] setEnabled:YES];
  198. }
  199. }
  200. %end
  201. %hook CommentsViewController
  202. %property(strong, nonatomic) id headerCellNode;
  203. - (void)moreOptionsBarButtonItemTappedWithSender:(id)arg1 {
  204. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  205. NSString *postBody = [post selfText];
  206. if ([post isSelfPost]){
  207. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:postBody isDeletedOnly:isTFDeletedOnly]){
  208. shouldAddUndeleteCell = YES;
  209. apolloCommentCell = nil;
  210. apolloCommentController = self;
  211. }
  212. }
  213. %orig;
  214. }
  215. %new
  216. - (void)undeleteCellWasSelected {
  217. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  218. [%c(TFHelper) getUndeleteDataWithID:[[post fullName] componentsSeparatedByString:@"_"][1] isComment:NO timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeletePostAction:)];
  219. }
  220. %new
  221. - (void)completeUndeletePostAction:(NSDictionary *)data {
  222. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  223. id headerCellNode = [self headerCellNode];
  224. id bodyNode = MSHookIvar<id>(headerCellNode, "bodyNode");
  225. id postInfoNode = MSHookIvar<id>(headerCellNode, "postInfoNode");
  226. id authorNode = MSHookIvar<id>(postInfoNode, "authorButtonNode");
  227. NSString *author = data[@"author"];
  228. NSString *authorTextString = [NSString stringWithFormat:@"by %@", author];
  229. NSString *body = data[@"body"];
  230. [post setUndeleteAuthor:author];
  231. [post setSelfText:body];
  232. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  233. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  234. NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:authorTextString attributes:authorStringAttributes];
  235. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  236. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  237. }
  238. %end
  239. %hook CommentsHeaderCellNode
  240. - (void)didLoad {
  241. %orig;
  242. [[self closestViewController] setHeaderCellNode:self];
  243. }
  244. - (void)_layoutSublayouts {
  245. %orig;
  246. [[self closestViewController] setHeaderCellNode:self];
  247. }
  248. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  249. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  250. NSString *postBody = [post selfText];
  251. if ([post isSelfPost]) {
  252. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:postBody isDeletedOnly:isTFDeletedOnly]){
  253. shouldAddUndeleteCell = YES;
  254. apolloCommentCell = nil;
  255. apolloCommentController = self;
  256. }
  257. }
  258. %orig;
  259. }
  260. %end
  261. %end
  262. static void loadPrefs(){
  263. NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
  264. if (prefs){
  265. isEnabled = [prefs objectForKey:@"isEnabled"] ? [[prefs objectForKey:@"isEnabled"] boolValue] : YES;
  266. isApolloEnabled = [prefs objectForKey:@"isApolloEnabled"] ? [[prefs objectForKey:@"isApolloEnabled"] boolValue] : YES;
  267. isTFDeletedOnly = [prefs objectForKey:@"isTFDeletedOnly"] ? [[prefs objectForKey:@"isTFDeletedOnly"] boolValue] : YES;
  268. pushshiftRequestTimeoutValue = [prefs objectForKey:@"requestTimeoutValue"] ? [[prefs objectForKey:@"requestTimeoutValue"] doubleValue] : 10;
  269. shouldApolloHaveButton = [prefs objectForKey:@"shouldApolloHaveButton"] ? [[prefs objectForKey:@"shouldApolloHaveButton"] boolValue] : NO;
  270. } else {
  271. isEnabled = YES;
  272. isApolloEnabled = YES;
  273. isTFDeletedOnly = YES;
  274. pushshiftRequestTimeoutValue = 10;
  275. shouldApolloHaveButton = NO;
  276. }
  277. }
  278. static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
  279. loadPrefs();
  280. }
  281. %ctor {
  282. loadPrefs();
  283. NSString* processName = [[NSProcessInfo processInfo] processName];
  284. if ([processName isEqualToString:@"Apollo"]){
  285. if (isApolloEnabled && isEnabled){
  286. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorCoalesce);
  287. %init(Apollo, CommentsHeaderCellNode = objc_getClass("Apollo.CommentsHeaderCellNode"), CommentCellNode = objc_getClass("Apollo.CommentCellNode"), ApolloButtonNode = objc_getClass("Apollo.ApolloButtonNode"), ActionController = objc_getClass("Apollo.ActionController"), IconActionTableViewCell = objc_getClass("Apollo.IconActionTableViewCell"), CommentsViewController = objc_getClass("Apollo.CommentsViewController"));
  288. }
  289. }
  290. }