WBStatusHelper.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. //
  2. // WBFeedHelper.m
  3. // YYKitExample
  4. //
  5. // Created by ibireme on 15/9/5.
  6. // Copyright (c) 2015 ibireme. All rights reserved.
  7. //
  8. #import "WBStatusHelper.h"
  9. @implementation WBStatusHelper
  10. + (NSBundle *)bundle {
  11. static NSBundle *bundle;
  12. static dispatch_once_t onceToken;
  13. dispatch_once(&onceToken, ^{
  14. NSString *path = [[NSBundle mainBundle] pathForResource:@"ResourceWeibo" ofType:@"bundle"];
  15. bundle = [NSBundle bundleWithPath:path];
  16. });
  17. return bundle;
  18. }
  19. + (NSBundle *)emoticonBundle {
  20. static NSBundle *bundle;
  21. static dispatch_once_t onceToken;
  22. dispatch_once(&onceToken, ^{
  23. NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"EmoticonWeibo" ofType:@"bundle"];
  24. bundle = [NSBundle bundleWithPath:bundlePath];
  25. });
  26. return bundle;
  27. }
  28. + (YYMemoryCache *)imageCache {
  29. static YYMemoryCache *cache;
  30. static dispatch_once_t onceToken;
  31. dispatch_once(&onceToken, ^{
  32. cache = [YYMemoryCache new];
  33. cache.shouldRemoveAllObjectsOnMemoryWarning = NO;
  34. cache.shouldRemoveAllObjectsWhenEnteringBackground = NO;
  35. cache.name = @"WeiboImageCache";
  36. });
  37. return cache;
  38. }
  39. + (UIImage *)imageNamed:(NSString *)name {
  40. if (!name) return nil;
  41. UIImage *image = [[self imageCache] objectForKey:name];
  42. if (image) return image;
  43. NSString *ext = name.pathExtension;
  44. if (ext.length == 0) ext = @"png";
  45. NSString *path = [[self bundle] pathForScaledResource:name ofType:ext];
  46. if (!path) return nil;
  47. image = [UIImage imageWithContentsOfFile:path];
  48. image = [image imageByDecoded];
  49. if (!image) return nil;
  50. [[self imageCache] setObject:image forKey:name];
  51. return image;
  52. }
  53. + (UIImage *)imageWithPath:(NSString *)path {
  54. if (!path) return nil;
  55. UIImage *image = [[self imageCache] objectForKey:path];
  56. if (image) return image;
  57. if (path.pathScale == 1) {
  58. // 查找 @2x @3x 的图片
  59. NSArray *scales = [NSBundle preferredScales];
  60. for (NSNumber *scale in scales) {
  61. image = [UIImage imageWithContentsOfFile:[path stringByAppendingPathScale:scale.floatValue]];
  62. if (image) break;
  63. }
  64. } else {
  65. image = [UIImage imageWithContentsOfFile:path];
  66. }
  67. if (image) {
  68. image = [image imageByDecoded];
  69. [[self imageCache] setObject:image forKey:path];
  70. }
  71. return image;
  72. }
  73. + (YYWebImageManager *)avatarImageManager {
  74. static YYWebImageManager *manager;
  75. static dispatch_once_t onceToken;
  76. dispatch_once(&onceToken, ^{
  77. NSString *path = [[UIApplication sharedApplication].cachesPath stringByAppendingPathComponent:@"weibo.avatar"];
  78. YYImageCache *cache = [[YYImageCache alloc] initWithPath:path];
  79. manager = [[YYWebImageManager alloc] initWithCache:cache queue:[YYWebImageManager sharedManager].queue];
  80. manager.sharedTransformBlock = ^(UIImage *image, NSURL *url) {
  81. if (!image) return image;
  82. return [image imageByRoundCornerRadius:100]; // a large value
  83. };
  84. });
  85. return manager;
  86. }
  87. + (NSString *)stringWithTimelineDate:(NSDate *)date {
  88. if (!date) return @"";
  89. static NSDateFormatter *formatterYesterday;
  90. static NSDateFormatter *formatterSameYear;
  91. static NSDateFormatter *formatterFullDate;
  92. static dispatch_once_t onceToken;
  93. dispatch_once(&onceToken, ^{
  94. formatterYesterday = [[NSDateFormatter alloc] init];
  95. [formatterYesterday setDateFormat:@"昨天 HH:mm"];
  96. [formatterYesterday setLocale:[NSLocale currentLocale]];
  97. formatterSameYear = [[NSDateFormatter alloc] init];
  98. [formatterSameYear setDateFormat:@"M-d"];
  99. [formatterSameYear setLocale:[NSLocale currentLocale]];
  100. formatterFullDate = [[NSDateFormatter alloc] init];
  101. [formatterFullDate setDateFormat:@"yy-M-dd"];
  102. [formatterFullDate setLocale:[NSLocale currentLocale]];
  103. });
  104. NSDate *now = [NSDate new];
  105. NSTimeInterval delta = now.timeIntervalSince1970 - date.timeIntervalSince1970;
  106. if (delta < -60 * 10) { // 本地时间有问题
  107. return [formatterFullDate stringFromDate:date];
  108. } else if (delta < 60 * 10) { // 10分钟内
  109. return @"刚刚";
  110. } else if (delta < 60 * 60) { // 1小时内
  111. return [NSString stringWithFormat:@"%d分钟前", (int)(delta / 60.0)];
  112. } else if (date.isToday) {
  113. return [NSString stringWithFormat:@"%d小时前", (int)(delta / 60.0 / 60.0)];
  114. } else if (date.isYesterday) {
  115. return [formatterYesterday stringFromDate:date];
  116. } else if (date.year == now.year) {
  117. return [formatterSameYear stringFromDate:date];
  118. } else {
  119. return [formatterFullDate stringFromDate:date];
  120. }
  121. }
  122. + (NSURL *)defaultURLForImageURL:(id)imageURL {
  123. /*
  124. 微博 API 提供的图片 URL 有时并不能直接用,需要做一些字符串替换:
  125. http://u1.sinaimg.cn/upload/2014/11/04/common_icon_membership_level6.png //input
  126. http://u1.sinaimg.cn/upload/2014/11/04/common_icon_membership_level6_default.png //output
  127. http://img.t.sinajs.cn/t6/skin/public/feed_cover/star_003_y.png?version=2015080302 //input
  128. http://img.t.sinajs.cn/t6/skin/public/feed_cover/star_003_os7.png?version=2015080302 //output
  129. */
  130. if (!imageURL) return nil;
  131. NSString *link = nil;
  132. if ([imageURL isKindOfClass:[NSURL class]]) {
  133. link = ((NSURL *)imageURL).absoluteString;
  134. } else if ([imageURL isKindOfClass:[NSString class]]) {
  135. link = imageURL;
  136. }
  137. if (link.length == 0) return nil;
  138. if ([link hasSuffix:@".png"]) {
  139. // add "_default"
  140. if (![link hasSuffix:@"_default.png"]) {
  141. NSString *sub = [link substringToIndex:link.length - 4];
  142. link = [sub stringByAppendingFormat:@"_default.png"];
  143. }
  144. } else {
  145. // replace "_y.png" with "_os7.png"
  146. NSRange range = [link rangeOfString:@"_y.png?version"];
  147. if (range.location != NSNotFound) {
  148. NSMutableString *mutable = link.mutableCopy;
  149. [mutable replaceCharactersInRange:NSMakeRange(range.location + 1, 1) withString:@"os7"];
  150. link = mutable;
  151. }
  152. }
  153. return [NSURL URLWithString:link];
  154. }
  155. + (NSString *)shortedNumberDesc:(NSUInteger)number {
  156. // should be localized
  157. if (number <= 9999) return [NSString stringWithFormat:@"%d", (int)number];
  158. if (number <= 9999999) return [NSString stringWithFormat:@"%d万", (int)(number / 10000)];
  159. return [NSString stringWithFormat:@"%d千万", (int)(number / 10000000)];
  160. }
  161. + (NSRegularExpression *)regexAt {
  162. static NSRegularExpression *regex;
  163. static dispatch_once_t onceToken;
  164. dispatch_once(&onceToken, ^{
  165. // 微博的 At 只允许 英文数字下划线连字符,和 unicode 4E00~9FA5 范围内的中文字符,这里保持和微博一致。。
  166. // 目前中文字符范围比这个大
  167. regex = [NSRegularExpression regularExpressionWithPattern:@"@[-_a-zA-Z0-9\u4E00-\u9FA5]+" options:kNilOptions error:NULL];
  168. });
  169. return regex;
  170. }
  171. + (NSRegularExpression *)regexTopic {
  172. static NSRegularExpression *regex;
  173. static dispatch_once_t onceToken;
  174. dispatch_once(&onceToken, ^{
  175. regex = [NSRegularExpression regularExpressionWithPattern:@"#[^@#]+?#" options:kNilOptions error:NULL];
  176. });
  177. return regex;
  178. }
  179. + (NSRegularExpression *)regexEmoticon {
  180. static NSRegularExpression *regex;
  181. static dispatch_once_t onceToken;
  182. dispatch_once(&onceToken, ^{
  183. regex = [NSRegularExpression regularExpressionWithPattern:@"\\[[^ \\[\\]]+?\\]" options:kNilOptions error:NULL];
  184. });
  185. return regex;
  186. }
  187. + (NSDictionary *)emoticonDic {
  188. static NSMutableDictionary *dic;
  189. static dispatch_once_t onceToken;
  190. dispatch_once(&onceToken, ^{
  191. NSString *emoticonBundlePath = [[NSBundle mainBundle] pathForResource:@"EmoticonWeibo" ofType:@"bundle"];
  192. dic = [self _emoticonDicFromPath:emoticonBundlePath];
  193. });
  194. return dic;
  195. }
  196. + (NSMutableDictionary *)_emoticonDicFromPath:(NSString *)path {
  197. NSMutableDictionary *dic = [NSMutableDictionary new];
  198. WBEmoticonGroup *group = nil;
  199. NSString *jsonPath = [path stringByAppendingPathComponent:@"info.json"];
  200. NSData *json = [NSData dataWithContentsOfFile:jsonPath];
  201. if (json.length) {
  202. group = [WBEmoticonGroup modelWithJSON:json];
  203. }
  204. if (!group) {
  205. NSString *plistPath = [path stringByAppendingPathComponent:@"info.plist"];
  206. NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
  207. if (plist.count) {
  208. group = [WBEmoticonGroup modelWithJSON:plist];
  209. }
  210. }
  211. for (WBEmoticon *emoticon in group.emoticons) {
  212. if (emoticon.png.length == 0) continue;
  213. NSString *pngPath = [path stringByAppendingPathComponent:emoticon.png];
  214. if (emoticon.chs) dic[emoticon.chs] = pngPath;
  215. if (emoticon.cht) dic[emoticon.cht] = pngPath;
  216. }
  217. NSArray *folders = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
  218. for (NSString *folder in folders) {
  219. if (folder.length == 0) continue;
  220. NSDictionary *subDic = [self _emoticonDicFromPath:[path stringByAppendingPathComponent:folder]];
  221. if (subDic) {
  222. [dic addEntriesFromDictionary:subDic];
  223. }
  224. }
  225. return dic;
  226. }
  227. + (NSArray<WBEmoticonGroup *> *)emoticonGroups {
  228. static NSMutableArray *groups;
  229. static dispatch_once_t onceToken;
  230. dispatch_once(&onceToken, ^{
  231. NSString *emoticonBundlePath = [[NSBundle mainBundle] pathForResource:@"EmoticonWeibo" ofType:@"bundle"];
  232. NSString *emoticonPlistPath = [emoticonBundlePath stringByAppendingPathComponent:@"emoticons.plist"];
  233. NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:emoticonPlistPath];
  234. NSArray *packages = plist[@"packages"];
  235. groups = (NSMutableArray *)[NSArray modelArrayWithClass:[WBEmoticonGroup class] json:packages];
  236. NSMutableDictionary *groupDic = [NSMutableDictionary new];
  237. for (int i = 0, max = (int)groups.count; i < max; i++) {
  238. WBEmoticonGroup *group = groups[i];
  239. if (group.groupID.length == 0) {
  240. [groups removeObjectAtIndex:i];
  241. i--;
  242. max--;
  243. continue;
  244. }
  245. NSString *path = [emoticonBundlePath stringByAppendingPathComponent:group.groupID];
  246. NSString *infoPlistPath = [path stringByAppendingPathComponent:@"info.plist"];
  247. NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:infoPlistPath];
  248. [group modelSetWithDictionary:info];
  249. if (group.emoticons.count == 0) {
  250. i--;
  251. max--;
  252. continue;
  253. }
  254. groupDic[group.groupID] = group;
  255. }
  256. NSArray<NSString *> *additionals = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[emoticonBundlePath stringByAppendingPathComponent:@"additional"] error:nil];
  257. for (NSString *path in additionals) {
  258. WBEmoticonGroup *group = groupDic[path];
  259. if (!group) continue;
  260. NSString *infoJSONPath = [[[emoticonBundlePath stringByAppendingPathComponent:@"additional"] stringByAppendingPathComponent:path] stringByAppendingPathComponent:@"info.json"];
  261. NSData *infoJSON = [NSData dataWithContentsOfFile:infoJSONPath];
  262. WBEmoticonGroup *addGroup = [WBEmoticonGroup modelWithJSON:infoJSON];
  263. if (addGroup.emoticons.count) {
  264. for (WBEmoticon *emoticon in addGroup.emoticons) {
  265. emoticon.group = group;
  266. }
  267. [((NSMutableArray *)group.emoticons) insertObjects:addGroup.emoticons atIndex:0];
  268. }
  269. }
  270. });
  271. return groups;
  272. }
  273. /*
  274. weibo.app 里面的正则,有兴趣的可以参考下:
  275. HTTP链接 (例如 http://www.weibo.com ):
  276. ([hH]ttp[s]{0,1})://[a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\-~!@#$%^&*+?:_/=<>.',;]*)?
  277. ([hH]ttp[s]{0,1})://[a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\-~!@#$%^&*+?:_/=<>]*)?
  278. (?i)https?://[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)+([-A-Z0-9a-z_\$\.\+!\*\(\)/,:;@&=\?~#%]*)*
  279. ^http?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+(\/[\w-. \/\?%@&+=\u4e00-\u9fa5]*)?$
  280. 链接 (例如 www.baidu.com/s?wd=test ):
  281. ^[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)+([-A-Z0-9a-z_\$\.\+!\*\(\)/,:;@&=\?~#%]*)*
  282. 邮箱 (例如 sjobs@apple.com ):
  283. \b([a-zA-Z0-9%_.+\-]{1,32})@([a-zA-Z0-9.\-]+?\.[a-zA-Z]{2,6})\b
  284. \b([a-zA-Z0-9%_.+\-]+)@([a-zA-Z0-9.\-]+?\.[a-zA-Z]{2,6})\b
  285. ([a-zA-Z0-9%_.+\-]+)@([a-zA-Z0-9.\-]+?\.[a-zA-Z]{2,6})
  286. 电话号码 (例如 18612345678):
  287. ^[1-9][0-9]{4,11}$
  288. At (例如 @王思聪 ):
  289. @([\x{4e00}-\x{9fa5}A-Za-z0-9_\-]+)
  290. 话题 (例如 #奇葩说# ):
  291. #([^@]+?)#
  292. 表情 (例如 [呵呵] ):
  293. \[([^ \[]*?)]
  294. 匹配单个字符 (中英文数字下划线连字符)
  295. [\x{4e00}-\x{9fa5}A-Za-z0-9_\-]
  296. 匹配回复 (例如 回复@王思聪: ):
  297. \x{56de}\x{590d}@([\x{4e00}-\x{9fa5}A-Za-z0-9_\-]+)(\x{0020}\x{7684}\x{8d5e})?:
  298. */
  299. @end