package io.kaif.web; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.*; import java.time.Instant; import java.util.Date; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.view.feed.AbstractRssFeedView; import com.rometools.rome.feed.rss.Channel; import com.rometools.rome.feed.rss.Description; import com.rometools.rome.feed.rss.Guid; import com.rometools.rome.feed.rss.Item; import io.kaif.model.article.Article; import io.kaif.model.zone.Zone; import io.kaif.model.zone.ZoneInfo; public class HotArticleRssContentView extends AbstractRssFeedView { private final static String SCHEME_AND_HOST = "https://kaif.io"; //kaif first date private final static Instant DEFAULT_INSTANT = Instant.ofEpochMilli(1424915802000L); private static String zoneUrl(Zone zone) { return SCHEME_AND_HOST + "/z/" + zone.value(); } private static String articleUrl(Article article) { return zoneUrl(article.getZone()) + "/debates/" + article.getArticleId().toString(); } public HotArticleRssContentView() { setContentType("application/rss+xml;charset=UTF-8"); } @Override protected void buildFeedMetadata(Map<String, Object> model, Channel feed, HttpServletRequest request) { ZoneInfo zoneInfo = (ZoneInfo) model.get("zoneInfo"); @SuppressWarnings("unchecked") List<Article> articles = (List<Article>) model.get("articles"); if (zoneInfo != null) { feed.setTitle(zoneInfo.getName() + " kaif.io"); feed.setLink(zoneUrl(zoneInfo.getZone())); feed.setDescription(zoneInfo.getAliasName() + " 熱門"); feed.setPubDate(buildFeedUpdateTime(articles, zoneInfo.getCreateTime())); } else { feed.setTitle("熱門 kaif.io"); feed.setLink(SCHEME_AND_HOST); feed.setDescription("綜合熱門"); feed.setPubDate(buildFeedUpdateTime(articles, DEFAULT_INSTANT)); } } private Date buildFeedUpdateTime(List<Article> articles, Instant fallbackTime) { return Date.from(articles.stream() .collect(maxBy(comparing(Article::getCreateTime))) .map(Article::getCreateTime) .orElse(fallbackTime)); } // XML 1.0 // #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] private static String XML10_PATTERN = "[^" + "\u0009" + "\u0020-\uD7FF" + "\uE000-\uFFFD" + "\ud800\udc00-\udbff\udfff" + "]"; private Item convertArticle(Article article) { Item entry = new Item(); Guid guid = new Guid(); guid.setValue(article.getArticleId().toString()); entry.setGuid(guid); entry.setTitle(cleanXml10Characters(article.getTitle())); entry.setPubDate(Date.from(article.getCreateTime())); Description summary = new Description(); summary.setType("html"); summary.setValue(buildSummary(article)); entry.setDescription(summary); entry.setLink(articleUrl(article)); return entry; } private String cleanXml10Characters(String input) { return input.replaceAll(XML10_PATTERN, ""); } private String buildAuthorPart(String username) { StringBuilder builder = new StringBuilder(); builder.append("由"); builder.append("<a href=\""); builder.append("https://kaif.io/u/"); builder.append(username); builder.append("\">"); builder.append(username); builder.append("</a>張貼<br>"); return builder.toString(); } private String buildSummary(Article article) { StringBuilder builder = new StringBuilder(); if (article.isExternalLink()) { builder.append(buildAuthorPart(article.getAuthorName())); builder.append("<a href=\""); builder.append(article.getLink()); builder.append("\">"); builder.append("[原文連結]"); builder.append("</a>"); } else { builder.append("<div>"); builder.append(article.getRenderContent()); builder.append("</div>"); builder.append(buildAuthorPart(article.getAuthorName())); } builder.append("<a href=\""); builder.append(articleUrl(article)); builder.append("\">["); builder.append(article.getDebateCount()); builder.append("個討論]"); builder.append("</a><br>"); return cleanXml10Characters(builder.toString()); } @Override protected List<Item> buildFeedItems(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { @SuppressWarnings("unchecked") List<Article> hotArticles = (List<Article>) model.get("articles"); return hotArticles.stream().map(this::convertArticle).collect(toList()); } }