diff options
9 files changed, 80 insertions, 24 deletions
diff --git a/juick-common/src/main/java/com/juick/service/SubscriptionService.java b/juick-common/src/main/java/com/juick/service/SubscriptionService.java index 8132ec10..98ea59e0 100644 --- a/juick-common/src/main/java/com/juick/service/SubscriptionService.java +++ b/juick-common/src/main/java/com/juick/service/SubscriptionService.java @@ -30,7 +30,7 @@ import java.util.List; public interface SubscriptionService { List<String> getJIDSubscribedToUser(int uid, boolean friendsonly); - List<User> getSubscribedUsers(int uid, int mid); + List<User> getSubscribedUsers(int uid, Message msg); List<User> getUsersSubscribedToComments(Message msg, Message reply); diff --git a/juick-common/src/main/java/com/juick/util/MessageUtils.java b/juick-common/src/main/java/com/juick/util/MessageUtils.java index bac24507..4de68cb9 100644 --- a/juick-common/src/main/java/com/juick/util/MessageUtils.java +++ b/juick-common/src/main/java/com/juick/util/MessageUtils.java @@ -68,6 +68,9 @@ public class MessageUtils { private final static String replyNumberRegex = "((?<=\\s)|(?<=\\A))\\/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))"; + private final static String usernameRegex = "((?<=\\s)|(?<=\\A))@([\\w\\-]{2,16})((?=\\s)|(?=\\Z)|(?=\\p{Punct}))"; + private final static Pattern usernamePattern = Pattern.compile(usernameRegex); + public static String formatMessageCode(String msg) { msg = msg.replaceAll("&", "&"); msg = msg.replaceAll("<", "<"); @@ -139,7 +142,7 @@ public class MessageUtils { // @username // <a href="http://juick.com/username/">@username</a> - msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-]{2,16})((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"https://juick.com/$2/\">@$2</a>$3"); + msg = msg.replaceAll(usernameRegex, "$1<a href=\"https://juick.com/$2/\">@$2</a>$3"); // (http://juick.com/last?page=2) // (<a href="http://juick.com/last?page=2" rel="nofollow">juick.com</a>) @@ -301,4 +304,12 @@ public class MessageUtils { } return input; } + public static List<String> getMentions(Message msg) { + Matcher usernameMatcher = usernamePattern.matcher(msg.getText()); + List<String> result = new ArrayList<>(); + while (usernameMatcher.find()) { + result.add(usernameMatcher.group()); + } + return result; + } } diff --git a/juick-common/src/test/java/com/juick/MessageTest.java b/juick-common/src/test/java/com/juick/MessageTest.java index aaa66af2..d9c83b84 100644 --- a/juick-common/src/test/java/com/juick/MessageTest.java +++ b/juick-common/src/test/java/com/juick/MessageTest.java @@ -22,6 +22,8 @@ import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Test; +import java.util.List; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -168,4 +170,12 @@ public class MessageTest { String msg = "[te](http://juick.com/)[st](http://juick.com/)"; assertThat(MessageUtils.stripNonSafeUrls(msg), is(msg)); } + @Test + public void mentionsCount() { + Message msg = new Message(); + msg.setText("@ugnich go home"); + List<String> mentions = MessageUtils.getMentions(msg); + assertThat(mentions.size(), is(1)); + assertThat(mentions.get(0), is("@ugnich")); + } } diff --git a/juick-server/src/main/java/com/juick/server/CommandsManager.java b/juick-server/src/main/java/com/juick/server/CommandsManager.java index 53b93d4c..e9cf11de 100644 --- a/juick-server/src/main/java/com/juick/server/CommandsManager.java +++ b/juick-server/src/main/java/com/juick/server/CommandsManager.java @@ -123,7 +123,7 @@ public class CommandsManager { subscriptionService.subscribeMessage(msg, user); applicationEventPublisher.publishEvent(new MessageReadEvent(this, Collections.singletonList(user), msg)); - applicationEventPublisher.publishEvent(new MessageEvent(this, msg, subscriptionService.getSubscribedUsers(msg.getUser().getUid(), msg.getMid()))); + applicationEventPublisher.publishEvent(new MessageEvent(this, msg, subscriptionService.getSubscribedUsers(msg.getUser().getUid(), msg))); return CommandResult.build(msg, "New message posted.\n#" + msg.getMid() + " https://juick.com/m/" + msg.getMid(), String.format("[New message](%s) posted", PlainTextFormatter.formatUrl(msg))); } diff --git a/juick-server/src/main/java/com/juick/server/EmailManager.java b/juick-server/src/main/java/com/juick/server/EmailManager.java index 0acabeff..2be21127 100644 --- a/juick-server/src/main/java/com/juick/server/EmailManager.java +++ b/juick-server/src/main/java/com/juick/server/EmailManager.java @@ -83,12 +83,12 @@ public class EmailManager implements ApplicationListener<MessageEvent> { headers.put("In-Reply-To", String.format("<%d.%d@juick.com>", original.getMid(), original.getRid())); } } - String plainText = String.format("%s\n\n--\nYou are receiving this because you are subscribed to this user " + - ", discussion or tag. Reply to this email directly or view it on Juick: %s.", + String plainText = String.format("%s\n\n--\nYou are receiving this because you are subscribed to this user," + + " discussion, tag or mentioned. Reply to this email directly or view it on Juick: %s.", formatPost(msg), formatUrl(msg)); String hash = userService.getHashByUID(userService.getUserByEmail(email).getUid()); String htmlText = String.format("%s<br /><br />--<br />You are receiving this because you are subscribed to this user" + - ", discussion or tag. Reply to this email directly or <a href=\"%s\"><img src=\"https://api.juick.com/thread/mark_read/%d-%d.gif?hash=%s\" />view it</a> on Juick." + + ", discussion, tag or mentioned. Reply to this email directly or <a href=\"%s\"><img src=\"https://api.juick.com/thread/mark_read/%d-%d.gif?hash=%s\" />view it</a> on Juick." + "<br /><a href=\"https://juick.com/settings?hash=%s\">Configure or disable notifications</a>", MessageUtils.formatHtml(msg), formatUrl(msg), msg.getMid(), msg.getRid(), hash, hash); diff --git a/juick-server/src/main/java/com/juick/server/XMPPConnection.java b/juick-server/src/main/java/com/juick/server/XMPPConnection.java index ba155fe7..fdbb5fe6 100644 --- a/juick-server/src/main/java/com/juick/server/XMPPConnection.java +++ b/juick-server/src/main/java/com/juick/server/XMPPConnection.java @@ -586,7 +586,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener { subscriptionService.getUsersSubscribedToComments(original, reply))); } else if (!MessageUtils.isPM(jmsg)) { applicationEventPublisher.publishEvent(new MessageEvent(this, - messagesService.getMessage(jmsg.getMid()), subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid()))); + messagesService.getMessage(jmsg.getMid()), subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg))); } } else { URI attachment = URI.create(StringUtils.EMPTY); diff --git a/juick-server/src/main/java/com/juick/server/api/Notifications.java b/juick-server/src/main/java/com/juick/server/api/Notifications.java index b0d64292..67e52851 100644 --- a/juick-server/src/main/java/com/juick/server/api/Notifications.java +++ b/juick-server/src/main/java/com/juick/server/api/Notifications.java @@ -89,7 +89,7 @@ public class Notifications { Message reply = messagesService.getReply(mid, rid); users = subscriptionService.getUsersSubscribedToComments(op, reply); } else { - users = subscriptionService.getSubscribedUsers(msg.getUser().getUid(), mid); + users = subscriptionService.getSubscribedUsers(msg.getUser().getUid(), msg); } return ResponseEntity.ok(users.stream().map(User::getUid) diff --git a/juick-server/src/main/java/com/juick/service/SubscriptionServiceImpl.java b/juick-server/src/main/java/com/juick/service/SubscriptionServiceImpl.java index 492fef1c..2032576c 100644 --- a/juick-server/src/main/java/com/juick/service/SubscriptionServiceImpl.java +++ b/juick-server/src/main/java/com/juick/service/SubscriptionServiceImpl.java @@ -21,6 +21,10 @@ import com.juick.Message; import com.juick.Tag; import com.juick.User; import com.juick.model.NotifyOpts; +import com.juick.util.MessageUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -61,16 +65,20 @@ public class SubscriptionServiceImpl extends BaseJdbcService implements Subscrip @Transactional(readOnly = true) @Override - public List<User> getSubscribedUsers(final int uid, final int mid) { + public List<User> getSubscribedUsers(final int uid, final Message msg) { + int mid = msg.getMid(); User author = messagesService.getMessageAuthor(mid); - List<User> userids = userService.getUserReaders(uid); + List<User> subscribers = userService.getUserReaders(uid); + List<User> mentionedUsers = userService.getUsersByName(MessageUtils.getMentions(msg).stream() + .map(u -> u.substring(1)).collect(Collectors.toList())); + List<User> users = ListUtils.union(subscribers, mentionedUsers); List<Integer> tags = tagService.getMessageTagsIDs(mid); List<String> tagsStr = tagService.getMessageTags(mid).stream().map(t -> t.getTag().getName()).collect(Collectors.toList()); Set<Integer> set = new HashSet<>(); set.addAll( - userids.stream() + users.stream() .map(User::getUid).filter(u -> Collections.disjoint(tagService.getUserBLTags(u), tagsStr)) .collect(Collectors.toList())); @@ -99,13 +107,15 @@ public class SubscriptionServiceImpl extends BaseJdbcService implements Subscrip @Override public List<User> getUsersSubscribedToComments(@Nonnull final Message msg, @Nonnull final Message reply, boolean blacklisted) { - List<Integer> userids = getJdbcTemplate().queryForList( + List<User> subscribers = userService.getUsersByID(getJdbcTemplate().queryForList( "SELECT suser_id FROM subscr_messages WHERE message_id=? AND suser_id!=?", Integer.class, - msg.getMid(), reply.getUser().getUid()); - - if (!userids.isEmpty()) { - return userService.getUsersByID(userids).stream() + msg.getMid(), reply.getUser().getUid())); + List<User> mentionedUsers = userService.getUsersByName(MessageUtils.getMentions(reply).stream() + .map(u -> u.substring(1)).collect(Collectors.toList())); + List<User> users = IteratorUtils.toList(CollectionUtils.union(subscribers, mentionedUsers).iterator()); + if (!users.isEmpty()) { + return users.stream() .filter(u -> blacklisted || !userService.isReplyToBL(u, reply)) .collect(Collectors.toList()); } diff --git a/juick-server/src/test/java/com/juick/server/tests/ServerTests.java b/juick-server/src/test/java/com/juick/server/tests/ServerTests.java index 7e5acf1a..0f483acc 100644 --- a/juick-server/src/test/java/com/juick/server/tests/ServerTests.java +++ b/juick-server/src/test/java/com/juick/server/tests/ServerTests.java @@ -251,7 +251,8 @@ public class ServerTests { Tag yoTag = tagService.getTag("yoyo", true); assertThat(tagService.getTag("YOYO", false), equalTo(yoTag)); int mid = messagesService.createMessage(ugnich.getUid(), "yo", null, Collections.singletonList(yoTag)); - List<User> subscribers = subscriptionService.getSubscribedUsers(ugnich.getUid(), mid); + Message msg = messagesService.getMessage(mid); + List<User> subscribers = subscriptionService.getSubscribedUsers(ugnich.getUid(), msg); telegramService.createTelegramUser(12345, "freefd"); String loginhash = jdbcTemplate.queryForObject("SELECT loginhash FROM telegram where tg_id=?", @@ -263,13 +264,18 @@ public class ServerTests { assertThat(subscribers.size(), equalTo(telegramSubscribers.size())); assertThat(subscribers.get(0).getUid(), equalTo(freefd.getUid())); tagService.blacklistTag(freefd, yoTag); - List<User> subscribers2 = subscriptionService.getSubscribedUsers(ugnich.getUid(), mid); + List<User> subscribers2 = subscriptionService.getSubscribedUsers(ugnich.getUid(), msg); assertThat(subscribers2.size(), equalTo(0)); assertThat(telegramService.getTelegramIdentifiers(subscribers2).size(), equalTo(0)); tagService.blacklistTag(freefd, yoTag); - assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), mid).size(), equalTo(1)); + assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), msg).size(), equalTo(1)); subscriptionService.unSubscribeUser(freefd, ugnich); - assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), mid).size(), equalTo(0)); + assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), msg).size(), equalTo(0)); + Message mentionMessage = new Message(); + mentionMessage.setText("@freefd - dick"); + assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), mentionMessage).size(), equalTo(1)); + subscriptionService.subscribeUser(freefd, ugnich); + assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), mentionMessage).size(), equalTo(1)); } @Test public void pmTests() { @@ -725,7 +731,8 @@ public class ServerTests { CommandResult yoyoMsg = commandsManager.processCommand(user, "*yo", URI.create("http://static.juick.com/settings/facebook.png")); assertTrue(yoyoMsg.getNewMessage().isPresent()); assertThat(yoyoMsg.getNewMessage().get().getTags().get(0), is(yo)); - int mid = yoyoMsg.getNewMessage().get().getMid(); + Message msg2 = yoyoMsg.getNewMessage().get(); + int mid = msg2.getMid(); Timestamp last = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", Timestamp.class, user.getUid()); assertThat(last.toInstant(), equalTo(yoyoMsg.getNewMessage().get().getTimestamp())); assertEquals("should be message", true, @@ -785,7 +792,7 @@ public class ServerTests { assertEquals("should be blacklisted", "Tag added to your blacklist", commandsManager.processCommand(readerUser, "BL *there", emptyUri).getText()); assertEquals("number of subscribed users should match", 0, - subscriptionService.getSubscribedUsers(uid, mid).size()); + subscriptionService.getSubscribedUsers(uid, msg2).size()); assertEquals("tags should be updated", "Tags are updated", commandsManager.processCommand(user, "#" + mid + " *there", emptyUri).getText()); assertEquals("number of tags should match", 1, @@ -795,11 +802,11 @@ public class ServerTests { assertEquals("should be subscribed", "Subscribed", commandsManager.processCommand(taggerUser, "S *yo", emptyUri).getText()); assertEquals("number of subscribed users should match", 2, - subscriptionService.getSubscribedUsers(uid, mid).size()); + subscriptionService.getSubscribedUsers(uid, msg2).size()); assertEquals("should be unsubscribed", "Unsubscribed from yo", commandsManager.processCommand(taggerUser, "U *yo", emptyUri).getText()); assertEquals("number of subscribed users should match", 1, - subscriptionService.getSubscribedUsers(uid, mid).size()); + subscriptionService.getSubscribedUsers(uid, msg2).size()); assertEquals("number of readers should match", 1, userService.getUserReaders(uid).size()); String readerFeed = commandsManager.processCommand(readerUser, "#", emptyUri).getText(); @@ -1266,6 +1273,24 @@ public class ServerTests { assertThat(subscribers.apply(recommenderId, messagesService.getMessage(readerMid)).size(), is(0)); } @Test + public void mentionsInComments() { + int posterId = userService.createUser("p", "secret"); + int commenterId = userService.createUser("cc", "secret"); + User commenter = userService.getUserByUID(commenterId).get(); + int mentionerId = userService.createUser("mmm", "secret"); + User mentioner = userService.getUserByUID(mentionerId).get(); + int mid = messagesService.createMessage(posterId, "who is dick?", null, null); + Message msg = messagesService.getMessage(mid); + int rid = messagesService.createReply(mid, 0, commenter, + "@mmm is dick", null); + Message reply = messagesService.getReply(mid, rid); + assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(1)); + subscriptionService.subscribeUser(mentioner, commenter); + assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(1)); + privacyQueriesService.blacklistUser(mentioner, commenter); + assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(0)); + } + @Test public void xmppStatusApi() throws Exception { Supplier<XMPPStatus> getStatus = () -> { try { |