No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

530 líneas
16KB

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