NewsEntryPoint.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Extensions;
  3. using MediaBrowser.Common.Net;
  4. using MediaBrowser.Controller.Library;
  5. using MediaBrowser.Controller.Notifications;
  6. using MediaBrowser.Controller.Plugins;
  7. using Microsoft.Extensions.Logging;
  8. using MediaBrowser.Model.News;
  9. using MediaBrowser.Model.Notifications;
  10. using MediaBrowser.Model.Serialization;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.Linq;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. using System.Xml;
  18. using MediaBrowser.Common.Progress;
  19. using MediaBrowser.Model.IO;
  20. using MediaBrowser.Model.Threading;
  21. namespace Emby.Server.Implementations.News
  22. {
  23. public class NewsEntryPoint : IServerEntryPoint
  24. {
  25. private ITimer _timer;
  26. private readonly IHttpClient _httpClient;
  27. private readonly IApplicationPaths _appPaths;
  28. private readonly IFileSystem _fileSystem;
  29. private readonly ILogger _logger;
  30. private readonly IJsonSerializer _json;
  31. private readonly INotificationManager _notifications;
  32. private readonly IUserManager _userManager;
  33. private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
  34. private readonly ITimerFactory _timerFactory;
  35. public NewsEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IJsonSerializer json, INotificationManager notifications, IUserManager userManager, ITimerFactory timerFactory)
  36. {
  37. _httpClient = httpClient;
  38. _appPaths = appPaths;
  39. _fileSystem = fileSystem;
  40. _logger = logger;
  41. _json = json;
  42. _notifications = notifications;
  43. _userManager = userManager;
  44. _timerFactory = timerFactory;
  45. }
  46. public void Run()
  47. {
  48. _timer = _timerFactory.Create(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
  49. }
  50. /// <summary>
  51. /// Called when [timer fired].
  52. /// </summary>
  53. /// <param name="state">The state.</param>
  54. private async void OnTimerFired(object state)
  55. {
  56. var path = Path.Combine(_appPaths.CachePath, "news.json");
  57. try
  58. {
  59. await DownloadNews(path).ConfigureAwait(false);
  60. }
  61. catch (Exception ex)
  62. {
  63. _logger.LogError(ex, "Error downloading news");
  64. }
  65. }
  66. private async Task DownloadNews(string path)
  67. {
  68. DateTime? lastUpdate = null;
  69. if (_fileSystem.FileExists(path))
  70. {
  71. lastUpdate = _fileSystem.GetLastWriteTimeUtc(path);
  72. }
  73. var requestOptions = new HttpRequestOptions
  74. {
  75. Url = "https://github.com/jellyfin/jellyfin",
  76. Progress = new SimpleProgress<double>(),
  77. UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36",
  78. BufferContent = false
  79. };
  80. using (var response = await _httpClient.SendAsync(requestOptions, "GET").ConfigureAwait(false))
  81. {
  82. using (var stream = response.Content)
  83. {
  84. using (var reader = XmlReader.Create(stream))
  85. {
  86. var news = ParseRssItems(reader).ToList();
  87. _json.SerializeToFile(news, path);
  88. await CreateNotifications(news, lastUpdate, CancellationToken.None).ConfigureAwait(false);
  89. }
  90. }
  91. }
  92. }
  93. private Task CreateNotifications(List<NewsItem> items, DateTime? lastUpdate, CancellationToken cancellationToken)
  94. {
  95. if (lastUpdate.HasValue)
  96. {
  97. items = items.Where(i => i.Date.ToUniversalTime() >= lastUpdate.Value)
  98. .ToList();
  99. }
  100. var tasks = items.Select(i => _notifications.SendNotification(new NotificationRequest
  101. {
  102. Date = i.Date,
  103. Name = i.Title,
  104. Description = i.Description,
  105. Url = i.Link,
  106. UserIds = _userManager.Users.Select(u => u.Id).ToArray()
  107. }, cancellationToken));
  108. return Task.WhenAll(tasks);
  109. }
  110. private IEnumerable<NewsItem> ParseRssItems(XmlReader reader)
  111. {
  112. reader.MoveToContent();
  113. reader.Read();
  114. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  115. {
  116. if (reader.NodeType == XmlNodeType.Element)
  117. {
  118. switch (reader.Name)
  119. {
  120. case "channel":
  121. {
  122. if (!reader.IsEmptyElement)
  123. {
  124. using (var subReader = reader.ReadSubtree())
  125. {
  126. return ParseFromChannelNode(subReader);
  127. }
  128. }
  129. else
  130. {
  131. reader.Read();
  132. }
  133. break;
  134. }
  135. default:
  136. {
  137. reader.Skip();
  138. break;
  139. }
  140. }
  141. }
  142. else
  143. {
  144. reader.Read();
  145. }
  146. }
  147. return new List<NewsItem>();
  148. }
  149. private IEnumerable<NewsItem> ParseFromChannelNode(XmlReader reader)
  150. {
  151. var list = new List<NewsItem>();
  152. reader.MoveToContent();
  153. reader.Read();
  154. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  155. {
  156. if (reader.NodeType == XmlNodeType.Element)
  157. {
  158. switch (reader.Name)
  159. {
  160. case "item":
  161. {
  162. if (!reader.IsEmptyElement)
  163. {
  164. using (var subReader = reader.ReadSubtree())
  165. {
  166. list.Add(ParseItem(subReader));
  167. }
  168. }
  169. else
  170. {
  171. reader.Read();
  172. }
  173. break;
  174. }
  175. default:
  176. {
  177. reader.Skip();
  178. break;
  179. }
  180. }
  181. }
  182. else
  183. {
  184. reader.Read();
  185. }
  186. }
  187. return list;
  188. }
  189. private NewsItem ParseItem(XmlReader reader)
  190. {
  191. var item = new NewsItem();
  192. reader.MoveToContent();
  193. reader.Read();
  194. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  195. {
  196. if (reader.NodeType == XmlNodeType.Element)
  197. {
  198. switch (reader.Name)
  199. {
  200. case "title":
  201. {
  202. item.Title = reader.ReadElementContentAsString();
  203. break;
  204. }
  205. case "link":
  206. {
  207. item.Link = reader.ReadElementContentAsString();
  208. break;
  209. }
  210. case "description":
  211. {
  212. item.DescriptionHtml = reader.ReadElementContentAsString();
  213. item.Description = item.DescriptionHtml.StripHtml();
  214. break;
  215. }
  216. case "pubDate":
  217. {
  218. var date = reader.ReadElementContentAsString();
  219. DateTime parsedDate;
  220. if (DateTime.TryParse(date, out parsedDate))
  221. {
  222. item.Date = parsedDate;
  223. }
  224. break;
  225. }
  226. default:
  227. {
  228. reader.Skip();
  229. break;
  230. }
  231. }
  232. }
  233. else
  234. {
  235. reader.Read();
  236. }
  237. }
  238. return item;
  239. }
  240. public void Dispose()
  241. {
  242. if (_timer != null)
  243. {
  244. _timer.Dispose();
  245. _timer = null;
  246. }
  247. }
  248. }
  249. }