diff --git a/.gitignore b/.gitignore index 82bb8a085..673a85142 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ packages .DS_Store Aweme Makefile.local +.vscode/ \ No newline at end of file diff --git a/AwemeHeaders.h b/AwemeHeaders.h index 540124b54..4c09205f0 100644 --- a/AwemeHeaders.h +++ b/AwemeHeaders.h @@ -323,6 +323,10 @@ typedef NS_ENUM(NSUInteger, DYEdgeMode) { - (void)buttonTouchUp:(id)sender; @end +@interface HTSLiveRoomStatsMessage : NSObject +- (NSInteger)displayValue; +@end + @interface AWEFeedVideoButton : UIButton @end diff --git a/DYYY.xm b/DYYY.xm index f4b8a1c80..ad15dc306 100644 --- a/DYYY.xm +++ b/DYYY.xm @@ -10,6 +10,7 @@ #import #import #import +#import #import "AwemeHeaders.h" #import "CityManager.h" @@ -362,6 +363,202 @@ static BOOL DYYYShouldHandleSpeedFeatures(void) { %end +// 直播间真实人数 + +static NSString *(*orig_displayShort)(id, SEL); +static NSString *my_displayShort(id self, SEL _cmd) { + if (!DYYYGetBool(@"DYYYEnableLiveRealCount")) return orig_displayShort(self, _cmd); + if ([self respondsToSelector:@selector(displayValue)]) { + NSInteger count = (NSInteger)[self performSelector:@selector(displayValue)]; + if (count > 0) return [NSString stringWithFormat:@"%ld", (long)count]; + } + return orig_displayShort(self, _cmd); +} + +static NSString *(*orig_displayMiddle)(id, SEL); +static NSString *my_displayMiddle(id self, SEL _cmd) { + if (!DYYYGetBool(@"DYYYEnableLiveRealCount")) return orig_displayMiddle(self, _cmd); + if ([self respondsToSelector:@selector(displayValue)]) { + NSInteger count = (NSInteger)[self performSelector:@selector(displayValue)]; + if (count > 0) return [NSString stringWithFormat:@"%ld", (long)count]; + } + return orig_displayMiddle(self, _cmd); +} + +static NSString *(*orig_displayLong)(id, SEL); +static NSString *my_displayLong(id self, SEL _cmd) { + if (!DYYYGetBool(@"DYYYEnableLiveRealCount")) return orig_displayLong(self, _cmd); + if ([self respondsToSelector:@selector(displayValue)]) { + NSInteger count = (NSInteger)[self performSelector:@selector(displayValue)]; + if (count > 0) return [NSString stringWithFormat:@"%ld在线观众", (long)count]; + } + return orig_displayLong(self, _cmd); +} + +// 评论具体时间 +%hook AWEDateTimeFormatter + ++ (id)formattedDateForTimestamp:(double)timestamp { + if (!DYYYGetBool(@"DYYYCommentExactTime")) return %orig(timestamp); + return [NSString stringWithFormat:@"%.0f ", timestamp]; +} + +%end + +%hook AWERLVirtualLabel + +- (void)setText:(NSString *)text { + if (!DYYYGetBool(@"DYYYCommentExactTime") || !text || text.length == 0) { + %orig(text); + return; + } + + if ([text isEqualToString:@"回复"]) { + %orig(@""); + return; + } + + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\d{10,13})([\\s\\S]*)" options:0 error:&error]; + + NSTextCheckingResult *match = [regex firstMatchInString:text options:0 range:NSMakeRange(0, text.length)]; + + if (match) { + NSString *rawTs = [text substringWithRange:[match rangeAtIndex:1]]; + NSString *suffix = [text substringWithRange:[match rangeAtIndex:2]]; + + long long ts = [rawTs longLongValue]; + + if (ts > 100000000000) { + ts = ts / 1000; + } + + NSDate *date = [NSDate dateWithTimeIntervalSince1970:ts]; + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + NSString *formattedDate = [formatter stringFromDate:date]; + + NSString *newText = [NSString stringWithFormat:@"%@%@", formattedDate, suffix]; + %orig(newText); + } else { + %orig(text); + } +} + +%end + +%group DYYYCommentExactTimeGroup +%hook AWECommentSwiftBizUI_CommentInteractionBaseLabel + +- (void)setText:(NSString *)text { + %orig(text); // 先让系统把文本赋上去 + + if (!DYYYGetBool(@"DYYYCommentExactTime")) { + return; + } + + UILabel *label = (UILabel *)self; + if (!text || text.length == 0) return; + + // --- 1. 拦截翻译文本,将其绝对定位在屏幕右侧 100 像素 --- + if ([text isEqualToString:@"翻译"] || [text isEqualToString:@"隐藏翻译"]) { + CGRect currentFrame = label.frame; + CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; + // 重新计算 X 坐标:屏幕宽度 - 100 - 标签自身宽度 + currentFrame.origin.x = screenWidth - 100.0 - currentFrame.size.width; + label.frame = currentFrame; + return; + } + + // --- 2. 拦截时间文本,如果不够宽则扩充宽度 --- + UIFont *font = label.font; + if (font) { + CGFloat expectedWidth = ceilf([text sizeWithAttributes:@{NSFontAttributeName: font}].width); + CGRect currentFrame = label.frame; + + // 如果当前宽度不够,并且不是尚未初始化的状态(>0),则强行修改并重新赋值 + if (currentFrame.size.width < expectedWidth && currentFrame.size.width > 0) { + currentFrame.size.width = expectedWidth; + label.frame = currentFrame; + label.clipsToBounds = NO; + } + } +} + +- (void)setFrame:(CGRect)frame { + if (!DYYYGetBool(@"DYYYCommentExactTime") || ![self respondsToSelector:@selector(text)]) { + %orig(frame); + return; + } + + UILabel *label = (UILabel *)self; + NSString *text = label.text; + + if (text && text.length > 0) { + // --- 1. 拦截翻译文本,将其绝对定位在屏幕右侧 100 像素 --- + if ([text isEqualToString:@"翻译"] || [text isEqualToString:@"隐藏翻译"]) { + CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; + frame.origin.x = screenWidth - 100.0 - frame.size.width; + } + // --- 2. 拦截时间文本,如果不够宽则扩充宽度 --- + else if ([self respondsToSelector:@selector(font)]) { + UIFont *font = label.font; + if (font) { + CGFloat expectedWidth = ceilf([text sizeWithAttributes:@{NSFontAttributeName: font}].width); + if (frame.size.width < expectedWidth && frame.size.width > 0) { + frame.size.width = expectedWidth; + label.clipsToBounds = NO; + } + } + } + } + + %orig(frame); +} + +%end +%end + +// 前面的AWEDateTimeFormatter会导致图文视频展开时间文本变成时间戳,这里处理下 +%hook YYLabel + +// 1. Hook 富文本赋值方法 (核心) +- (void)setAttributedText:(NSAttributedString *)attributedText { + if (!DYYYGetBool(@"DYYYCommentExactTime") || !attributedText || attributedText.length == 0) { + %orig(attributedText); + return; + } + + NSString *plainText = [attributedText string]; + + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\d{10,13})" options:0 error:&error]; + NSTextCheckingResult *match = [regex firstMatchInString:plainText options:0 range:NSMakeRange(0, plainText.length)]; + + if (match) { + NSString *rawTs = [plainText substringWithRange:[match rangeAtIndex:1]]; + long long ts = [rawTs longLongValue]; + + if (ts > 100000000000) { + ts = ts / 1000; + } + + NSDate *date = [NSDate dateWithTimeIntervalSince1970:ts]; + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + NSString *formattedDate = [formatter stringFromDate:date]; + + NSMutableAttributedString *newAttrStr = [attributedText mutableCopy]; + [newAttrStr replaceCharactersInRange:[match rangeAtIndex:1] withString:formattedDate]; + + %orig(newAttrStr); + } else { + %orig(attributedText); + } +} + +%end + // 禁用自动进入直播间 %hook AWELiveGuideElement @@ -8508,6 +8705,18 @@ static void findTargetViewInView(UIView *view) { } %ctor { + Class interactionBaseLabelClass = objc_getClass("AWECommentSwiftBizUI.CommentInteractionBaseLabel"); + if (interactionBaseLabelClass) { + %init(DYYYCommentExactTimeGroup, AWECommentSwiftBizUI_CommentInteractionBaseLabel = interactionBaseLabelClass); + } + + Class statsMessageClass = objc_getClass("HTSLiveRoomStatsMessage"); + if (statsMessageClass) { + MSHookMessageEx(statsMessageClass, @selector(displayShort), (IMP)&my_displayShort, (IMP *)&orig_displayShort); + MSHookMessageEx(statsMessageClass, @selector(displayMiddle), (IMP)&my_displayMiddle, (IMP *)&orig_displayMiddle); + MSHookMessageEx(statsMessageClass, @selector(displayLong), (IMP)&my_displayLong, (IMP *)&orig_displayLong); + } + Class imMenuComponentClass = objc_getClass("AWEIMCustomMenuComponent"); if (imMenuComponentClass) { SEL legacySelector = NSSelectorFromString(@"msg_showMenuForBubbleFrameInScreen:tapLocationInScreen:menuItemList:moreEmoticon:onCell:extra:"); diff --git a/DYYYSettings.xm b/DYYYSettings.xm index d19b5dcd4..ec2afbe34 100644 --- a/DYYYSettings.xm +++ b/DYYYSettings.xm @@ -496,6 +496,19 @@ void showDYYYSettingsVC(UIViewController *rootVC, BOOL hasAgreed) { @"detail" : @"自动", @"cellType" : @26, @"imageName" : @"ic_video_outlined_20"}, + @{@"identifier" : @"DYYYEnableLiveRealCount", + @"title" : @"直播真实人数", + @"subTitle" : @"直播显示具体的在线人数", + @"detail" : @"", + @"cellType" : @37, + @"imageName" : @"ic_video_outlined_20"}, + + @{@"identifier" : @"DYYYCommentExactTime", + @"title" : @"评论具体时间", + @"subTitle" : @"开启后评论区将显示具体的发布时间而非相对时间", + @"detail" : @"", + @"cellType" : @37, + @"imageName" : @"ic_clock_outlined_20"}, @{@"identifier" : @"DYYYEnableVideoHighestQuality", @"title" : @"提高视频画质", @"detail" : @"",