/* * Copyright (C) 2008-2017, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.juick.www.controllers; import com.juick.Message; import com.juick.Tag; import com.juick.formatters.PlainTextFormatter; import com.juick.server.component.MessageReadEvent; import com.juick.server.util.HttpForbiddenException; import com.juick.server.util.HttpNotFoundException; import com.juick.server.util.UserUtils; import com.juick.server.util.WebUtils; import com.juick.service.*; import com.juick.util.MessageUtils; import com.juick.www.Utils; import org.apache.commons.codec.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.text.StringEscapeUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; import ru.sape.Sape; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * * @author Ugnich Anton */ @Controller public class MessagesWWW { @Inject private UserService userService; @Inject private TagService tagService; @Inject private MessagesService messagesService; @Inject private Sape sape; @Inject private PMQueriesService pmQueriesService; @Inject private CrosspostService crosspostService; @Inject private ApplicationEventPublisher applicationEventPublisher; void fillUserModel(ModelMap model, com.juick.User user, com.juick.User visitor) { model.addAttribute("user", user); model.addAttribute("isSubscribed", userService.isSubscribed(visitor.getUid(), user.getUid())); model.addAttribute("isInBL", userService.isInBL(visitor.getUid(), user.getUid())); model.addAttribute("isInBLAny", userService.isInBLAny(user.getUid(), visitor.getUid())); model.addAttribute("statsIRead", userService.getStatsIRead(user.getUid())); model.addAttribute("statsMyReaders", userService.getStatsMyReaders(user.getUid())); model.addAttribute("statsMyBL", userService.getUserBLUsers(user.getUid()).size()); model.addAttribute("statsMessages", userService.getStatsMessages(user.getUid())); model.addAttribute("statsReplies", userService.getStatsReplies(user.getUid())); model.addAttribute("iread", userService.getUserReadLeastPopular(user.getUid(), 8)); model.addAttribute("tagStats", tagService.getUserTagStats(user.getUid()) .stream().sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).limit(20).map(t -> t.getTag().getName()).collect(Collectors.toList())); } @GetMapping("/") protected String doGet( @RequestParam(required = false) String tag, @RequestParam(name = "show", required = false) String paramShow, @RequestParam(name = "search", required = false) String paramSearch, @RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore, @RequestParam(name = "to", required = false, defaultValue = "0") Long paramTo, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, ModelMap model) throws IOException { if (tag != null) { return "redirect:/tag/" + URLEncoder.encode(tag, CharEncoding.UTF_8); } com.juick.User visitor = UserUtils.getCurrentUser(); if (paramSearch != null && paramSearch.length() > 64) { paramSearch = null; } model.addAttribute("discover", false); String title; List mids; if (paramSearch != null) { title = "Поиск: " + StringEscapeUtils.escapeHtml4(paramSearch); mids = messagesService.getSearch(Utils.encodeSphinx(paramSearch), paramBefore); } else if (paramShow == null) { if (!visitor.isAnonymous()) { title = "Популярные"; mids = messagesService.getPopular(visitor.getUid(), paramBefore); model.addAttribute("discover", true); } else { title = "Микроблоги Juick: популярные записи"; mids = messagesService.getPopular(0, paramBefore); } } else if (paramShow.equals("top")) { return "redirect:/"; } else if (paramShow.equals("my") && !visitor.isAnonymous()) { title = "Моя лента"; mids = messagesService.getMyFeed(visitor.getUid(), paramBefore, true); } else if (paramShow.equals("private") && !visitor.isAnonymous()) { title = "Приватные"; mids = messagesService.getPrivate(visitor.getUid(), paramBefore); } else if (paramShow.equals("discuss") && !visitor.isAnonymous()) { title = "Обсуждения"; mids = messagesService.getDiscussions(visitor.getUid(), paramTo); } else if (paramShow.equals("recommended") && !visitor.isAnonymous()) { title = "Рекомендации"; mids = messagesService.getRecommended(visitor.getUid(), paramBefore); } else if (paramShow.equals("photos")) { title = "Фотографии"; mids = messagesService.getPhotos(visitor.getUid(), paramBefore); model.addAttribute("discover", true); } else if (paramShow.equals("all")) { title = "Все сообщения"; mids = messagesService.getAll(visitor.getUid(), paramBefore); model.addAttribute("discover", true); } else { throw new HttpNotFoundException(); } String head = StringUtils.EMPTY; if (paramBefore > 0 || paramShow != null) { head = ""; } model.addAttribute("title", title); model.addAttribute("headers", head); model.addAttribute("visitor", visitor); model.addAttribute("noindex", !(paramShow == null && paramBefore == 0)); List msgs = messagesService.getMessages(mids); if (!visitor.isAnonymous()) { fillUserModel(model, visitor, visitor); List unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); List blUIDs = userService.checkBL(visitor.getUid(), msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); msgs.forEach(m -> { m.ReadOnly |= blUIDs.contains(m.getUser().getUid()); m.setUnread(unread.contains(m.getMid())); }); } model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("headers", head); model.addAttribute("showAdv", paramShow == null && paramBefore == 0 && paramSearch == null && visitor.isAnonymous()); if (mids.size() >= 20) { String nextpage = (paramShow != null && paramShow.equals("discuss")) ? "?to=" + msgs.get(msgs.size() - 1).getUpdated().toEpochMilli() : "?before=" + mids.get(mids.size() - 1); if (paramShow != null) { nextpage += "&show=" + paramShow; } if (paramSearch != null) { nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); } model.addAttribute("nextpage", nextpage); } UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); String queryString = builder.getQuery(); String requestURI = builder.toUri().getPath(); if (sape != null && visitor.isAnonymous() && queryString == null) { String links = sape.getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } return "views/index"; } @GetMapping("/{uname}/") protected String doGetBlog( @RequestParam(required = false, name = "show") String paramShow, @RequestParam(required = false, name = "tag") String paramTagStr, @RequestParam(required = false, name = "search") String paramSearch, @PathVariable String uname, @RequestParam(required = false, defaultValue = "0") Integer before, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, ModelMap model) throws IOException { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (user.isBanned() || user.isAnonymous()) { throw new HttpNotFoundException(); } List mids; com.juick.Tag paramTag = null; if (paramTagStr != null) { if (paramTagStr.length() < 64) { paramTag = tagService.getTag(paramTagStr, false); } if (paramTag == null) { throw new HttpNotFoundException(); } else if (!paramTag.getName().equals(paramTagStr)) { String url = user.getName() + "/?tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); return "redirect:/" + url; } } if (paramSearch != null && paramSearch.length() > 64) { paramSearch = null; } int privacy = 0; if (!visitor.isAnonymous()) { if (user.getUid() == visitor.getUid() || visitor.getUid() == 1) { privacy = -3; } else if (userService.isInWL(user.getUid(), visitor.getUid())) { privacy = -2; } } String title; if (paramShow == null) { if (paramTag != null) { title = "Блог " + user.getName() + ": *" + StringEscapeUtils.escapeHtml4(paramTag.getName()); mids = messagesService.getUserTag(user.getUid(), paramTag.TID, privacy, before); } else if (paramSearch != null) { title = "Блог " + user.getName() + ": " + StringEscapeUtils.escapeHtml4(paramSearch); mids = messagesService.getUserSearch(user.getUid(), Utils.encodeSphinx(paramSearch), privacy, before); } else { title = "Блог " + user.getName(); mids = messagesService.getUserBlog(user.getUid(), privacy, before); } } else if (paramShow.equals("recomm")) { title = "Рекомендации " + user.getName(); mids = messagesService.getUserRecommendations(user.getUid(), before); } else if (paramShow.equals("photos")) { title = "Фотографии " + user.getName(); mids = messagesService.getUserPhotos(user.getUid(), privacy, before); } else { throw new HttpNotFoundException(); } String head = ""; if (paramTag != null && tagService.getTagNoIndex(paramTag.TID)) { head += ""; } else if (before > 0 || paramShow != null) { head += ""; } model.addAttribute("pageUrl", "http://juick.com/" + user.getName()); model.addAttribute("title", title); model.addAttribute("headers", head); model.addAttribute("visitor", visitor); model.addAttribute("noindex", paramShow == null && before == 0); fillUserModel(model, user, visitor); model.addAttribute("paramTag", paramTag); List msgs = messagesService.getMessages(mids); if (!visitor.isAnonymous()) { List unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); List blUIDs = userService.checkBL(visitor.getUid(), msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); msgs.forEach(m -> { m.ReadOnly |= blUIDs.contains(m.getUser().getUid()); m.setUnread(unread.contains(m.getMid())); }); } model.addAttribute("msgs", msgs); model.addAttribute("headers", head); model.addAttribute("showAdv", paramShow == null && before == 0 && paramSearch == null && visitor.getUid() == 0); if (mids.size() >= 20) { String nextpage = "?before=" + mids.get(mids.size() - 1); if (paramShow != null) { nextpage += "&show=" + paramShow; } if (paramSearch != null) { nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); } if (paramTag != null) { nextpage += "&tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); } model.addAttribute("nextpage", nextpage); } UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); String queryString = builder.getQuery(); String requestURI = builder.toUri().getPath(); if (sape != null && visitor.isAnonymous() && queryString == null) { String links = sape.getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } return "views/blog"; } @GetMapping("/{uname}/tags") protected String doGetTags(@PathVariable String uname, ModelMap model) throws IOException { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isBanned()) { throw new HttpNotFoundException(); } model.addAttribute("title", "Теги " + user.getName()); model.addAttribute("headers", ""); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("tags", tagService.getUserTagStats(user.getUid()).stream() .sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).map(t -> t.getTag().getName()).collect(Collectors.toList())); return "views/blog_tags"; } @GetMapping("/{uname}/friends") protected String doGetFriends(@PathVariable String uname, ModelMap model) throws IOException { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isBanned()) { throw new HttpNotFoundException(); } model.addAttribute("title", "Подписки " + user.getName()); model.addAttribute("headers", ""); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("users", userService.getUserFriends(user.getUid())); return "views/users"; } @GetMapping("/{uname}/readers") protected String doGetReaders(@PathVariable String uname, ModelMap model) throws IOException { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isBanned()) { throw new HttpForbiddenException(); } model.addAttribute("title", "Читатели " + user.getName()); model.addAttribute("headers", ""); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("users", userService.getUserReaders(user.getUid())); return "views/users"; } @GetMapping("/{uname}/bl") protected String doGetBL(@PathVariable String uname, ModelMap model) throws IOException { com.juick.User user = userService.getUserByName(uname); com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isBanned() || visitor.getUid() != user.getUid()) { throw new HttpForbiddenException(); } model.addAttribute("title", "Черный список " + user.getName()); model.addAttribute("headers", ""); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("users", userService.getUserBLUsers(user.getUid())); return "views/users"; } @GetMapping("/tag/{tagName}") protected String tagAction(HttpServletRequest request, @PathVariable String tagName, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, @RequestParam(required = false, defaultValue = "0") int before, ModelMap model) throws IOException { com.juick.User visitor = UserUtils.getCurrentUser(); String paramTagStr = StringEscapeUtils.unescapeHtml4(tagName); com.juick.Tag paramTag = tagService.getTag(paramTagStr, false); if (paramTag == null) { throw new HttpNotFoundException(); } else if (paramTag.SynonymID > 0 && paramTag.TID != paramTag.SynonymID) { com.juick.Tag synTag = tagService.getTag(paramTag.SynonymID); String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(synTag.getName()), CharEncoding.UTF_8); if (request.getQueryString() != null) { url += "?" + request.getQueryString(); } return "redirect:" + url; } else if (!paramTag.getName().equals(paramTagStr)) { String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(paramTag.getName()), CharEncoding.UTF_8); if (request.getQueryString() != null) { url += "?" + request.getQueryString(); } return "redirect:" + url; } String title = "*" + StringEscapeUtils.escapeHtml4(paramTag.getName()); model.addAttribute("title", title); List mids = messagesService.getTag(paramTag.TID, visitor.getUid(), before, (visitor.isAnonymous()) ? 40 : 20); List msgs = messagesService.getMessages(mids); if (!visitor.isAnonymous()) { List unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); List blUIDs = userService.checkBL( visitor.getUid(), msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList()) ); msgs.forEach(m -> { m.ReadOnly |= blUIDs.contains(m.getUser().getUid()); m.setUnread(unread.contains(m.getMid())); }); fillUserModel(model, visitor, visitor); } String head = StringUtils.EMPTY; if (tagService.getTagNoIndex(paramTag.TID)) { head = ""; } else if (before > 0 || mids.size() < 5) { head = ""; } model.addAttribute("headers", head); model.addAttribute("visitor", visitor); model.addAttribute("tag", paramTag); model.addAttribute("title", title); model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("noindex", before > 0); model.addAttribute("showAdv", before == 0 && visitor.isAnonymous()); model.addAttribute("isSubscribed", tagService.isSubscribed(visitor, paramTag)); model.addAttribute("isInBL", tagService.isInBL(visitor, paramTag)); if (mids.size() >= 20) { String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8) + "?before=" + mids.get(mids.size() - 1); model.addAttribute("nextpage", nextpage); } UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); String queryString = builder.getQuery(); String requestURI = builder.toUri().getPath(); if (sape != null && visitor.isAnonymous() && queryString == null) { String links = sape.getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } return "views/index"; } @GetMapping("/pm/inbox") protected String doGetInbox(ModelMap model) { com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isAnonymous()) { return "redirect:/login"; } String title = "PM: Inbox"; List msgs = pmQueriesService.getLastPMInbox(visitor); fillUserModel(model, visitor, visitor); model.addAttribute("title", title); model.addAttribute("visitor", visitor); model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); return "views/pm_inbox"; } @GetMapping("/pm/sent") protected String doGetSent(@RequestParam(required = false) String uname, ModelMap model) { com.juick.User visitor = UserUtils.getCurrentUser(); if (visitor.isAnonymous()) { return "redirect:/login"; } String title = "PM: Sent"; List msgs = pmQueriesService.getLastPMSent(visitor); if (WebUtils.isNotUserName(uname)) { uname = StringUtils.EMPTY; } fillUserModel(model, visitor, visitor); model.addAttribute("title", title); model.addAttribute("visitor", visitor); model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("uname", uname); return "views/pm_sent"; } @GetMapping("/{uname}/{mid}") protected String threadAction(ModelMap model, @PathVariable String uname, @PathVariable int mid, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie) { com.juick.User visitor = UserUtils.getCurrentUser(); if (!messagesService.canViewThread(mid, visitor.getUid())) { throw new HttpForbiddenException(); } com.juick.Message msg = messagesService.getMessage(mid); if (msg == null || msg.getUser().isBanned()) { throw new HttpNotFoundException(); } com.juick.User user = userService.getUserByName(uname); if (user.isAnonymous() || !msg.getUser().equals(user)) { return String.format("redirect:/%s/%d", msg.getUser().getName(), mid); } msg.VisitorCanComment = !visitor.isAnonymous(); List replies = messagesService.getReplies(visitor, msg.getMid()); // this should be after getReplies to mark thread as read fillUserModel(model, user, visitor); if (!visitor.isAnonymous()) { List unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); boolean isInBL = userService.isInBLAny(msg.getUser().getUid(), visitor.getUid()); msg.VisitorCanComment = isMsgAuthor || !(msg.ReadOnly || isInBL); } model.addAttribute("msg", msg); String title = msg.getUser().getName() + ": " + MessageUtils.getTagsString(msg); model.addAttribute("title", title); model.addAttribute("visitor", visitor); String headers = ""; String pageUrl = "https://juick.com/" + msg.getUser().getName() + "/" + msg.getMid(); if (msg.Hidden) { headers += ""; } String cardType = StringUtils.isNotEmpty(msg.getAttachmentType()) ? "summary_large_image" : "summary"; if (StringUtils.isNotEmpty(msg.getAttachmentType())) { // additional check in case of broken images if (msg.getAttachment() != null) { String msgImage = msg.getAttachment().getMedium().getUrl(); headers += ""; } } else { String msgImage ="https://i.juick.com/a/" + msg.getUser().getUid() + ".png"; headers += ""; } model.addAttribute("ogtype", "article"); String cardDescription = StringEscapeUtils.escapeHtml4(PlainTextFormatter.formatTwitterCard(msg)); headers += "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; String twitterName = crosspostService.getTwitterName(msg.getUser()); if (StringUtils.isNotEmpty(twitterName)) { headers += "\n"; } if (msg.getTags().size() > 0) { headers += "\n"; } model.addAttribute("headers", headers); model.addAttribute("visitorSubscribed", messagesService.isSubscribed(visitor.getUid(), msg.getMid())); model.addAttribute("visitorInBL", userService.isInBL(msg.getUser().getUid(), visitor.getUid())); model.addAttribute("recomm", messagesService.getMessageRecommendations(msg.getMid())); List blUIDs = new ArrayList<>(); for (Message reply : replies) { if (reply.getUser().getUid() != msg.getUser().getUid() && !blUIDs.contains(reply.getUser().getUid())) { blUIDs.add(reply.getUser().getUid()); } reply.VisitorCanComment = !visitor.isAnonymous(); if (!visitor.isAnonymous()) { boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); boolean isReplyAuthor = visitor.getUid() == reply.getUser().getUid(); reply.VisitorCanComment = isMsgAuthor || (!msg.ReadOnly && msg.VisitorCanComment && (isReplyAuthor || !userService.isInBLAny(visitor.getUid(), reply.getUser().getUid()))); } } model.addAttribute("replies", replies); model.addAttribute("showAdv", visitor.isAnonymous()); UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); String queryString = builder.getQuery(); String requestURI = builder.toUri().getPath(); if (sape != null && visitor.isAnonymous() && queryString == null) { String links = sape.getPageLinks(requestURI, sapeCookie).render(); model.addAttribute("links", links); } return "views/thread"; } // when message id is not fit to int @ExceptionHandler(NumberFormatException.class) public ResponseEntity notFoundAction() { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } }