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.

554 lines
17KB

  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. BOOL shouldAddUndeleteCellForContext = NO;
  14. id apolloCommentCellForContext;
  15. id apolloCommentsControllerForContext;
  16. %hook ApolloButtonNode
  17. %end
  18. %hook IconActionTableViewCell
  19. %end
  20. %hook RKComment
  21. - (BOOL)isDeleted {
  22. return NO;
  23. }
  24. - (BOOL)isModeratorRemoved {
  25. return NO;
  26. }
  27. %end
  28. //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
  29. %hook RDKComment
  30. - (BOOL)isDeleted {
  31. return NO;
  32. }
  33. - (BOOL)isModeratorRemoved {
  34. return NO;
  35. }
  36. %end
  37. %hook RKLink
  38. %property(strong, nonatomic) NSString *undeleteAuthor;
  39. - (id)author {
  40. return [self undeleteAuthor] ? [self undeleteAuthor] : %orig;
  41. }
  42. %end
  43. %hook RDKLink
  44. %property(strong, nonatomic) NSString *undeleteAuthor;
  45. - (id)author {
  46. return [self undeleteAuthor] ? [self undeleteAuthor] : %orig;
  47. }
  48. %end
  49. %hook MarkdownRenderer
  50. + (id)attributedStringFromHTML:(id)arg1 attributes:(id)arg2 compact:(BOOL)arg3 {
  51. apolloBodyAttributes = [arg2 copy];
  52. return %orig;
  53. }
  54. // fix for v1.10.6 no longer getting text attributes, meaning text color was not showing properly
  55. + (id)attributedStringFromHTML:(id)arg1 attributes:(id)arg2 compact:(BOOL)arg3 snoomojiMapping:(id)arg4 {
  56. apolloBodyAttributes = [arg2 copy];
  57. return %orig;
  58. }
  59. + (id)attributedStringFromHTML:(id)arg1 attributes:(id)arg2 compact:(BOOL)arg3 snoomojiMapping:(id)arg4 snoomojiInfo:(id)arg5 {
  60. apolloBodyAttributes = [arg2 copy];
  61. return %orig;
  62. }
  63. %end
  64. %hook ActionController
  65. - (id)tableView:(id)arg1 cellForRowAtIndexPath:(NSIndexPath *)arg2 {
  66. if (shouldAddUndeleteCell) {
  67. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1) {
  68. id undeleteCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell" forIndexPath:arg2];
  69. id prevCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell"]; // is this necessary?
  70. UIImageView *prevCellImageView = MSHookIvar<UIImageView *>(prevCell, "iconImageView");
  71. CGSize prevImageSize = [[prevCellImageView image] size];
  72. UIImage *undeleteImage;
  73. //if (@available(iOS 13.0, *)){
  74. if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"13.0")) {
  75. undeleteImage = [UIImage systemImageNamed:@"eye"];
  76. if (!undeleteImage){
  77. undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  78. } else {
  79. CGSize squareSize = CGSizeMake(undeleteImage.size.width, undeleteImage.size.width);
  80. UIGraphicsBeginImageContextWithOptions(squareSize, NO, 0);
  81. [undeleteImage drawInRect:CGRectMake(0, (squareSize.height - undeleteImage.size.height) / 2, squareSize.width, undeleteImage.size.height)];
  82. undeleteImage = UIGraphicsGetImageFromCurrentImageContext();
  83. UIGraphicsEndImageContext();
  84. }
  85. } else {
  86. undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  87. }
  88. CGFloat undeleteImageSizeValue = prevImageSize.width > prevImageSize.height ? prevImageSize.width : prevImageSize.height;
  89. if (undeleteImageSizeValue == 0) {
  90. undeleteImageSizeValue = 25;
  91. }
  92. UIGraphicsBeginImageContextWithOptions(CGSizeMake(undeleteImageSizeValue, undeleteImageSizeValue), NO, 0);
  93. [undeleteImage drawInRect:CGRectMake(0, 0, undeleteImageSizeValue, undeleteImageSizeValue)];
  94. undeleteImage = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  95. UIGraphicsEndImageContext();
  96. UILabel *undeleteLabel = MSHookIvar<UILabel *>(undeleteCell, "actionTitleLabel");
  97. UIImageView *undeleteImageView = MSHookIvar<UIImageView *>(undeleteCell, "iconImageView");
  98. undeleteLabel.text = @"TF Did That Say?";
  99. undeleteImageView.image = undeleteImage;
  100. // fix undelete cell being green when moderator cell is present
  101. UIColor *contentColor = [UIColor colorWithRed:0.137 green:0.6 blue:1 alpha:1];
  102. [undeleteImageView setTintColor:contentColor];
  103. NSMutableAttributedString *newAttribuedText = [[NSMutableAttributedString alloc] initWithAttributedString:undeleteLabel.attributedText];
  104. [newAttribuedText addAttribute:NSForegroundColorAttributeName value:contentColor range:NSMakeRange(0, [newAttribuedText length])];
  105. undeleteLabel.attributedText = newAttribuedText;
  106. MSHookIvar<UIImageView *>(undeleteCell, "disclosureIndicator").image = nil;
  107. return undeleteCell;
  108. }
  109. }
  110. return %orig;
  111. }
  112. - (void)tableView:(id)arg1 didSelectRowAtIndexPath:(NSIndexPath *)arg2 {
  113. if (shouldAddUndeleteCell) {
  114. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1){
  115. if (apolloCommentCell) {
  116. [apolloCommentCell undeleteCellWasSelected];
  117. } else {
  118. [apolloCommentController undeleteCellWasSelected];
  119. }
  120. }
  121. }
  122. %orig;
  123. }
  124. - (NSInteger)tableView:(id)arg1 numberOfRowsInSection:(NSInteger)arg2 {
  125. if (shouldAddUndeleteCell){
  126. return %orig + 1;
  127. } else {
  128. return %orig;
  129. }
  130. }
  131. - (id)animationControllerForDismissedController:(id)arg1 {
  132. shouldAddUndeleteCell = NO;
  133. return %orig;
  134. }
  135. %end
  136. %hook CommentCellNode
  137. %property(strong,nonatomic) UIButton *undeleteButton;
  138. - (void)moreOptionsTappedWithSender:(id)arg1 {
  139. if (!shouldApolloHaveButton){
  140. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  141. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  142. shouldAddUndeleteCell = YES;
  143. apolloCommentCell = self;
  144. apolloCommentController = nil;
  145. }
  146. }
  147. %orig;
  148. }
  149. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  150. if (!shouldApolloHaveButton){
  151. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  152. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  153. shouldAddUndeleteCell = YES;
  154. apolloCommentCell = self;
  155. apolloCommentController = nil;
  156. }
  157. }
  158. %orig;
  159. }
  160. - (void)didLoad {
  161. %orig;
  162. if (shouldApolloHaveButton) {
  163. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  164. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  165. CGFloat imageSize = 20.0f;
  166. UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  167. [undeleteButton addTarget:self action:@selector(didTapUndeleteButton:) forControlEvents:UIControlEventTouchUpInside];
  168. undeleteButton.frame = CGRectMake(0, 0, imageSize, imageSize);
  169. UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  170. [undeleteButton setImage:undeleteImage forState:UIControlStateNormal];
  171. [[self view] addSubview:undeleteButton];
  172. [self setUndeleteButton:undeleteButton];
  173. }
  174. } else {
  175. id actionDelegate = MSHookIvar<id>(self, "actionDelegate");
  176. [actionDelegate setCommentCellNode:self];
  177. }
  178. }
  179. - (void)_layoutSublayouts {
  180. %orig;
  181. if (shouldApolloHaveButton) {
  182. if ([self undeleteButton]) {
  183. CGFloat imageSize = 20.0f;
  184. id moreNode = MSHookIvar<id>(self, "moreOptionsNode");
  185. id ageNode = MSHookIvar<id>(self, "ageNode");
  186. CGRect nodeFrame = [moreNode frame];
  187. CGFloat centerHeight = (nodeFrame.size.height + nodeFrame.origin.y * 2) / 2.0f;
  188. CGFloat nodeSpacing = [ageNode frame].origin.x - nodeFrame.origin.x - nodeFrame.size.width;
  189. [[self undeleteButton] setFrame:CGRectMake(nodeFrame.origin.x - imageSize - nodeSpacing, centerHeight - (imageSize / 2), imageSize, imageSize)];
  190. }
  191. }
  192. }
  193. %new
  194. - (void)didTapUndeleteButton:(id)sender {
  195. [sender setEnabled:NO];
  196. id comment = MSHookIvar<id>(self, "comment");
  197. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:@{@"sender" : sender} completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  198. }
  199. %new
  200. - (void)undeleteCellWasSelected {
  201. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  202. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  203. }
  204. %new
  205. - (void)completeUndeleteCommentAction:(NSDictionary *)data {
  206. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  207. id bodyNode = MSHookIvar<id>(self, "bodyNode");
  208. id authorNode = MSHookIvar<id>(self, "authorNode");
  209. NSString *author = data[@"author"];
  210. NSString *body = data[@"body"];
  211. [comment setAuthor:author];
  212. [comment setBody:body];
  213. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  214. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  215. NSAttributedString *newAuthorAttributedString = [[NSAttributedString alloc] initWithString:author attributes:authorStringAttributes];
  216. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  217. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  218. if ([data objectForKey:@"sender"]) {
  219. [data[@"sender"] setEnabled:YES];
  220. }
  221. }
  222. %end
  223. %hook CommentsViewController
  224. %property(strong, nonatomic) id headerCellNode;
  225. - (void)moreOptionsBarButtonItemTappedWithSender:(id)arg1 {
  226. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  227. NSString *author = [post author];
  228. if ([post isSelfPost]) {
  229. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  230. shouldAddUndeleteCell = YES;
  231. apolloCommentCell = nil;
  232. apolloCommentController = self;
  233. }
  234. }
  235. %orig;
  236. }
  237. %new
  238. - (void)undeleteCellWasSelected {
  239. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  240. [%c(TFHelper) getUndeleteDataWithID:[[post fullName] componentsSeparatedByString:@"_"][1] isComment:NO timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeletePostAction:)];
  241. }
  242. %new
  243. - (void)completeUndeletePostAction:(NSDictionary *)data {
  244. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  245. id headerCellNode = [self headerCellNode];
  246. id bodyNode = MSHookIvar<id>(headerCellNode, "bodyNode");
  247. id postInfoNode = MSHookIvar<id>(headerCellNode, "postInfoNode");
  248. id authorNode = MSHookIvar<id>(postInfoNode, "authorButtonNode");
  249. NSString *author = data[@"author"];
  250. NSString *authorTextString = [NSString stringWithFormat:@"by %@", author];
  251. NSString *body = data[@"body"];
  252. [post setUndeleteAuthor:author];
  253. [post setSelfText:body];
  254. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  255. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  256. NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:authorTextString attributes:authorStringAttributes];
  257. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  258. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  259. }
  260. %end
  261. %hook CommentsHeaderCellNode
  262. - (void)didLoad {
  263. %orig;
  264. [[self closestViewController] setHeaderCellNode:self];
  265. }
  266. - (void)_layoutSublayouts {
  267. %orig;
  268. [[self closestViewController] setHeaderCellNode:self];
  269. }
  270. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  271. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  272. NSString *author = [post author];
  273. if ([post isSelfPost]) {
  274. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]){
  275. shouldAddUndeleteCell = YES;
  276. apolloCommentCell = nil;
  277. apolloCommentController = self;
  278. }
  279. }
  280. %orig;
  281. }
  282. %end
  283. %hook UIMenu
  284. - (id)menuByReplacingChildren:(id)arg5 {
  285. if (shouldAddUndeleteCellForContext) {
  286. UIAction *action = [UIAction actionWithTitle:@"TF Did That Say?" image:[UIImage systemImageNamed:@"eye"] identifier:@"testident" handler:^(__kindof UIAction* _Nonnull action) {
  287. id commentCell = apolloCommentCellForContext;
  288. id commentsController = apolloCommentsControllerForContext;
  289. if (commentCell) {
  290. [commentCell undeleteCellWasSelected];
  291. } else {
  292. [commentsController undeleteCellWasSelected];
  293. }
  294. }];
  295. arg5 = [arg5 arrayByAddingObject:action];
  296. }
  297. return %orig;
  298. }
  299. %end
  300. %hook CommentSectionController
  301. %property(strong, nonatomic) id commentCellNode;
  302. - (id)contextMenuInteraction:(id)arg1 configurationForMenuAtLocation:(CGPoint)arg2 {
  303. if (!shouldApolloHaveButton) {
  304. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  305. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  306. shouldAddUndeleteCellForContext = YES;
  307. apolloCommentCellForContext = [self commentCellNode];
  308. apolloCommentsControllerForContext = nil;
  309. }
  310. }
  311. return %orig;
  312. }
  313. %new
  314. - (void)contextMenuInteraction:(id)arg1 willEndForConfiguration:(id)arg2 animator:(id)arg3 {
  315. shouldAddUndeleteCellForContext = NO;
  316. apolloCommentCellForContext = nil;
  317. apolloCommentsControllerForContext = nil;
  318. }
  319. %end
  320. %hook CommentsHeaderSectionController
  321. - (id)contextMenuInteraction:(id)arg1 configurationForMenuAtLocation:(CGPoint)arg2 {
  322. id commentsController = MSHookIvar<id>(self, "viewController");
  323. RKLink *post = MSHookIvar<RKLink *>(commentsController, "link");
  324. NSString *author = [post author];
  325. if ([post isSelfPost]) {
  326. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  327. shouldAddUndeleteCellForContext = YES;
  328. apolloCommentCellForContext = nil;
  329. apolloCommentsControllerForContext = commentsController;
  330. }
  331. }
  332. return %orig;
  333. }
  334. %new
  335. - (void)contextMenuInteraction:(id)arg1 willEndForConfiguration:(id)arg2 animator:(id)arg3 {
  336. shouldAddUndeleteCellForContext = NO;
  337. apolloCommentCellForContext = nil;
  338. apolloCommentsControllerForContext = nil;
  339. }
  340. %end
  341. %end
  342. static void loadPrefs(){
  343. NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
  344. if (prefs){
  345. isEnabled = [prefs objectForKey:@"isEnabled"] ? [[prefs objectForKey:@"isEnabled"] boolValue] : YES;
  346. isApolloEnabled = [prefs objectForKey:@"isApolloEnabled"] ? [[prefs objectForKey:@"isApolloEnabled"] boolValue] : YES;
  347. isTFDeletedOnly = [prefs objectForKey:@"isTFDeletedOnly"] ? [[prefs objectForKey:@"isTFDeletedOnly"] boolValue] : YES;
  348. pushshiftRequestTimeoutValue = [prefs objectForKey:@"requestTimeoutValue"] ? [[prefs objectForKey:@"requestTimeoutValue"] doubleValue] : 10;
  349. shouldApolloHaveButton = [prefs objectForKey:@"shouldApolloHaveButton"] ? [[prefs objectForKey:@"shouldApolloHaveButton"] boolValue] : NO;
  350. } else {
  351. isEnabled = YES;
  352. isApolloEnabled = YES;
  353. isTFDeletedOnly = YES;
  354. pushshiftRequestTimeoutValue = 10;
  355. shouldApolloHaveButton = NO;
  356. }
  357. }
  358. static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
  359. loadPrefs();
  360. }
  361. %ctor {
  362. loadPrefs();
  363. NSString* processName = [[NSProcessInfo processInfo] processName];
  364. if ([processName isEqualToString:@"Apollo"]){
  365. if (isApolloEnabled && isEnabled){
  366. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorCoalesce);
  367. %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"), CommentSectionController = objc_getClass("Apollo.CommentSectionController"), CommentsHeaderSectionController = objc_getClass("Apollo.CommentsHeaderSectionController"));
  368. }
  369. }
  370. }