From 240a375ecdeb7592d8765f0edb7d2fd67c50ac10 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 30 Oct 2017 13:52:55 +0300 Subject: xmpp: bot refactoring --- .../com/juick/server/protocol/JuickProtocol.java | 241 +--------------- .../juick/configuration/MockDataConfiguration.java | 4 + .../main/java/com/juick/ws/WebsocketComponent.java | 51 +--- .../main/java/com/juick/components/XMPPBot.java | 53 +--- .../main/java/com/juick/components/JuickBot.java | 318 ++++++++++++++------- .../java/com/juick/components/s2s/Connection.java | 2 + .../com/juick/xmpp/server/XMPPServerTests.java | 12 +- 7 files changed, 233 insertions(+), 448 deletions(-) diff --git a/juick-server-core/src/main/java/com/juick/server/protocol/JuickProtocol.java b/juick-server-core/src/main/java/com/juick/server/protocol/JuickProtocol.java index 1d5b8dbf..9cb37151 100644 --- a/juick-server-core/src/main/java/com/juick/server/protocol/JuickProtocol.java +++ b/juick-server-core/src/main/java/com/juick/server/protocol/JuickProtocol.java @@ -21,18 +21,18 @@ import com.juick.Message; import com.juick.Tag; import com.juick.User; import com.juick.formatters.PlainTextFormatter; -import com.juick.server.helpers.TagStats; import com.juick.server.protocol.annotation.UserCommand; import com.juick.server.util.TagUtils; import com.juick.service.*; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.reflect.MethodUtils; import javax.inject.Inject; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -106,150 +106,8 @@ public class JuickProtocol { return "New message posted.\n#" + mid + " " + baseUri + mid; } - @UserCommand(pattern = "^#\\+$", help = "#+ - Show last Juick messages") - public String commandLast(User user, String... arguments) { - List mids = messagesService.getAll(user.getUid(), 0); - List messages = messagesService.getMessages(mids); - return "Last messages: \n" - + messages.stream().sorted(Collections.reverseOrder()).map(PlainTextFormatter::formatPostSummary) - .collect(Collectors.joining("\n\n")); - } - - @UserCommand(pattern = "^bl$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "BL - Show your blacklist") - public String commandBL(User user_from, String... arguments) { - List blusers; - List bltags; - - blusers = userService.getUserBLUsers(user_from.getUid()); - bltags = tagService.getUserBLTags(user_from.getUid()); - - - String txt = StringUtils.EMPTY; - if (bltags.size() > 0) { - for (String bltag : bltags) { - txt += "*" + bltag + "\n"; - } - - if (blusers.size() > 0) { - txt += "\n"; - } - } - if (blusers.size() > 0) { - for (User bluser : blusers) { - txt += "@" + bluser.getName() + "\n"; - } - } - if (txt.isEmpty()) { - txt = "You don't have any users or tags in your blacklist."; - } - return txt; - } - @UserCommand(pattern = "^bl\\s+@([^\\s\\n\\+]+)", patternFlags = Pattern.CASE_INSENSITIVE, - help = "BL @username - add @username to your blacklist") - public String blacklistUser(User from, String... arguments) { - User blUser = userService.getUserByName(arguments[0]); - if (blUser != null) { - PrivacyQueriesService.PrivacyResult result = privacyQueriesService.blacklistUser(from, blUser); - if (result == PrivacyQueriesService.PrivacyResult.Added) { - return "User added to your blacklist"; - } else { - return "User removed from your blacklist"; - } - } - return "User not found"; - } - @UserCommand(pattern = "^bl\\s\\*(\\S+)$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "BL *tag - add *tag to your blacklist") - public String blacklistTag(User from, String... arguments) { - User blUser = userService.getUserByName(arguments[0]); - if (blUser != null) { - Tag tag = tagService.getTag(arguments[0], false); - if (tag != null) { - PrivacyQueriesService.PrivacyResult result = privacyQueriesService.blacklistTag(from, tag); - if (result == PrivacyQueriesService.PrivacyResult.Added) { - return "Tag added to your blacklist"; - } else { - return "Tag removed from your blacklist"; - } - } - } - return "Tag not found"; - } - - @UserCommand(pattern = "@", help = "@ - Show recommendations and popular personal blogs") - public String commandUsers(User currentUser, String... args) { - StringBuilder msg = new StringBuilder(); - msg.append("Recommended blogs"); - List recommendedUsers = showQueriesService.getRecommendedUsers(currentUser); - if (recommendedUsers.size() > 0) { - for (String user : recommendedUsers) { - msg.append("\n@").append(user); - } - } else { - msg.append("\nNo recommendations now. Subscribe to more blogs. ;)"); - } - msg.append("\n\nTop 10 personal blogs:"); - List topUsers = showQueriesService.getTopUsers(); - if (topUsers.size() > 0) { - for (String user : topUsers) { - msg.append("\n@").append(user); - } - } else { - msg.append("\nNo top users. Empty DB? ;)"); - } - return msg.toString(); - } - - @UserCommand(pattern = "\\*", help = "* - Show your tags") - public String commandTags(User currentUser, String... args) { - List tags = tagService.getUserTagStats(currentUser.getUid()); - String msg = "Your tags: (tag - messages)\n" + - tags.stream() - .map(t -> String.format("\n*%s - %d", t.getTag().getName(), t.getUsageCount())).collect(Collectors.joining()); - return msg; - } - - @UserCommand(pattern = "S", help = "S - Show your subscriptions") - public String commandSubscriptions(User currentUser, String... args) { - List friends = userService.getUserFriends(currentUser.getUid()); - List tags = subscriptionService.getSubscribedTags(currentUser); - String msg = friends.size() > 0 ? "You are subscribed to users:" + friends.stream().map(u -> "\n@" + u.getName()) - .collect(Collectors.joining()) - : "You are not subscribed to any user."; - msg += tags.size() > 0 ? "\nYou are subscribed to tags:" + tags.stream().map(t -> "\n*" + t) - .collect(Collectors.joining()) - : "\nYou are not subscribed to any tag."; - return msg; - } - - @UserCommand(pattern = "!", help = "! - Show your favorite messages") - public String commandFavorites(User currentUser, String... args) { - List mids = messagesService.getUserRecommendations(currentUser.getUid(), 0); - if (mids.size() > 0) { - List messages = messagesService.getMessages(mids); - return "Favorite messages: \n" + String.join("\n", messages.stream().map(PlainTextFormatter::formatPost) - .collect(Collectors.toList())); - } - return "No favorite messages, try to \"like\" something ;)"; - } - - @UserCommand(pattern = "^\\@([^\\s\\n\\+]+)(\\+?)$", - help = "@username+ - Show user's info and last 10 messages (@username++ - second page, ..)") - public String commandUser(User user, String... arguments) { - User blogUser = userService.getUserByName(arguments[0]); - int page = arguments[1].length(); - if (blogUser.getUid() > 0) { - List mids = messagesService.getUserBlog(blogUser.getUid(), 0, page); - List messages = messagesService.getMessages(mids); - return String.format("Last messages from @%s:\n%s", arguments[0], - String.join("\n", messages.stream() - .map(PlainTextFormatter::formatPost).collect(Collectors.toList()))); - } - return "User not found"; - } @UserCommand(pattern = "^d\\s*\\#([0-9]+)$", patternFlags = Pattern.CASE_INSENSITIVE, help = "D #12345 - delete the message") @@ -261,11 +119,6 @@ public class JuickProtocol { return "Error"; } - @UserCommand(pattern = "^login$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "LOGIN - log in to Juick website") - public String commandLogin(User user, String... arguments) { - return baseUri + "?" + userService.getHashByUID(user.getUid()); - } @UserCommand(pattern = "^(#+)$", help = "# - Show last messages from your feed (## - second page, ...)") public String commandMyFeed(User user, String... arguments) { @@ -278,49 +131,7 @@ public class JuickProtocol { messages.stream().map(PlainTextFormatter::formatPost).collect(Collectors.toList())); } - @UserCommand(pattern = "^(on|off)$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "ON/OFF - Enable/disable subscriptions delivery") - public String commandOnOff(User user, String[] input) { - UserService.ActiveStatus newStatus; - String retValUpdated; - if (input[0].toLowerCase().equals("on")) { - newStatus = UserService.ActiveStatus.Active; - retValUpdated = "Notifications are activated for " + user.getJid(); - } else { - newStatus = UserService.ActiveStatus.Inactive; - retValUpdated = "Notifications are disabled for " + user.getJid(); - } - - if (userService.setActiveStatusForJID(user.getJid(), newStatus)) { - return retValUpdated; - } else { - return String.format("Subscriptions status for %s was not changed", user.getJid()); - } - } - - @UserCommand(pattern = "^ping$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "PING - returns you a PONG") - public String commandPing(User user, String[] input) { - return "PONG"; - } - - @UserCommand(pattern = "^\\@(\\S+)\\s+([\\s\\S]+)$", help = "@username message - send PM to username") - public String commandPM(User user_from, String... arguments) { - String user_to = arguments[0]; - String body = arguments[1]; - - User toUser = userService.getUserByName(user_to); - if (toUser.getUid() > 0) { - if (!userService.isInBLAny(toUser.getUid(), user_from.getUid())) { - if (pmQueriesService.createPM(user_from.getUid(), toUser.getUid(), body)) { - listener.privateMessage(user_from, toUser, body); - return "Private message sent"; - } - } - } - return "Error"; - } @UserCommand(pattern = "^#(\\d+)(\\+?)$", help = "#1234 - Show message (#1234+ - message with replies)") public String commandShow(User user, String... arguments) { @@ -362,25 +173,7 @@ public class JuickProtocol { } } - @UserCommand(pattern = "^(s|u)\\s+#(\\d+)$", help = "S #1234 - subscribe to comments", - patternFlags = Pattern.CASE_INSENSITIVE) - public String commandSubscribeMessage(User user, String... args) { - boolean subscribe = args[0].equalsIgnoreCase("s"); - int mid = NumberUtils.toInt(args[1], 0); - if (messagesService.getMessage(mid) != null) { - if (subscribe) { - if (subscriptionService.subscribeMessage(mid, user.getUid())) { - return "Subscribed"; - } - } else { - if (subscriptionService.unSubscribeMessage(mid, user.getUid())) { - return "Unsubscribed from #" + mid; - } - return "You was not subscribed to #" + mid; - } - } - return "Error"; - } + @UserCommand(pattern = "^(s|u)\\s+\\@(\\S+)$", help = "S @user - subscribe to user's posts", patternFlags = Pattern.CASE_INSENSITIVE) public String commandSubscribeUser(User user, String... args) { @@ -402,32 +195,6 @@ public class JuickProtocol { } return "Error"; } - @UserCommand(pattern = "^(s|u)\\s+\\*(\\S+)$", help = "S *tag - subscribe to tag" + - "\nU *tag - unsubscribe from tag", patternFlags = Pattern.CASE_INSENSITIVE) - public String commandSubscribeTag(User user, String... args) { - boolean subscribe = args[0].equalsIgnoreCase("s"); - Tag tag = tagService.getTag(args[1], true); - if (subscribe) { - if (subscriptionService.subscribeTag(user, tag)) { - return "Subscribed"; - } - } else { - if (subscriptionService.unSubscribeTag(user, tag)) { - return "Unsubscribed from " + tag.getName(); - } - return "You was not subscribed to " + tag.getName(); - } - return "Error"; - } - - @UserCommand(pattern = "^help$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "HELP - returns this help message") - public String commandHelp(User user, String[] input) { - return Arrays.stream(getClass().getDeclaredMethods()) - .filter(m -> m.isAnnotationPresent(UserCommand.class)) - .map(m -> m.getAnnotation(UserCommand.class).help()) - .collect(Collectors.joining("\n")); - } public String getBaseUri() { return baseUri; diff --git a/juick-server-jdbc/src/test/java/com/juick/configuration/MockDataConfiguration.java b/juick-server-jdbc/src/test/java/com/juick/configuration/MockDataConfiguration.java index a4e21bc6..65c12d30 100644 --- a/juick-server-jdbc/src/test/java/com/juick/configuration/MockDataConfiguration.java +++ b/juick-server-jdbc/src/test/java/com/juick/configuration/MockDataConfiguration.java @@ -66,4 +66,8 @@ public class MockDataConfiguration { ShowQueriesService showQueriesService() { return Mockito.mock(ShowQueriesService.class); } + @Bean + PrivacyQueriesService privacyQueriesService() { + return Mockito.mock(PrivacyQueriesService.class); + } } diff --git a/juick-ws/src/main/java/com/juick/ws/WebsocketComponent.java b/juick-ws/src/main/java/com/juick/ws/WebsocketComponent.java index bdf6dba6..599d1394 100644 --- a/juick-ws/src/main/java/com/juick/ws/WebsocketComponent.java +++ b/juick-ws/src/main/java/com/juick/ws/WebsocketComponent.java @@ -51,7 +51,7 @@ import java.util.List; /** * Created by vitalyster on 28.06.2016. */ -public class WebsocketComponent extends TextWebSocketHandler implements ProtocolListener { +public class WebsocketComponent extends TextWebSocketHandler { private static final Logger logger = LoggerFactory.getLogger(WebsocketComponent.class); private final List clients = Collections.synchronizedList(new LinkedList<>()); @@ -62,13 +62,7 @@ public class WebsocketComponent extends TextWebSocketHandler implements Protocol private MessagesService messagesService; @Inject private SubscriptionService subscriptionService; - @Inject - private JuickProtocol protocol; - @PostConstruct - public void init() { - protocol.setListener(this); - } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { @@ -148,53 +142,10 @@ public class WebsocketComponent extends TextWebSocketHandler implements Protocol } }); } - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { - getClients().stream().filter(s -> !s.legacy && s.session.equals(session)).forEach(s -> { - User user = s.visitor; - String input = message.getPayload(); - if (StringUtils.isNotBlank(input)) { - try { - s.session.sendMessage(new TextMessage(protocol.getReply(user, input))); - } catch (IOException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { - logger.error("protocol exception", e); - } - } - }); - } - public List getClients() { return clients; } - @Override - public void privateMessage(User from, User to, String body) { - notifyUser(from, to, "Private message from @" + from.getName() + ":\n" + body); - } - - @Override - public void userSubscribed(User from, User to) { - notifyUser(from, to, String.format("@%s subscribed to your blog", from.getName())); - } - - @Override - public void messagePosted(Message msg) { - subscriptionService.getSubscribedUsers(msg.getUser().getUid(), msg.getMid()).forEach(u -> { - notifyUser(msg.getUser(), u, msg.getText()); - }); - } - - private void notifyUser(User from, User to, String body) { - getClients().stream().filter(s -> !s.legacy && s.visitor.equals(to)).forEach(s -> { - try { - s.session.sendMessage(new TextMessage(body)); - } catch (IOException e) { - logger.error("protocol exception", e); - } - }); - } - class SocketSubscribed { WebSocketSession session; String clientName; diff --git a/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java index fa097584..7cd0cbc0 100644 --- a/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java +++ b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java @@ -47,15 +47,13 @@ import java.util.List; /** * Created by vt on 12/11/2016. */ -public class XMPPBot implements AutoCloseable, ProtocolListener { +public class XMPPBot implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(XMPPBot.class); @Inject private UserService userService; @Inject private PMQueriesService pmQueriesService; @Inject - private JuickProtocol juickProtocol; - @Inject private Environment env; Jid juickJid; @@ -69,7 +67,6 @@ public class XMPPBot implements AutoCloseable, ProtocolListener { env.getProperty("component_password", "secret"), env.getProperty("component_host", "localhost"), NumberUtils.toInt(env.getProperty("component_port", "5347"), 5347)); juickJid = Jid.of(env.getProperty("xmppbot_jid", "juick@juick.com/Juick")); - juickProtocol.setListener(this); try { SoftwareVersionManager softwareVersionManager = component.getManager(SoftwareVersionManager.class); softwareVersionManager.setSoftwareVersion(new SoftwareVersion("Juick", "git", System.getProperty("os.name", "generic"))); @@ -100,16 +97,6 @@ public class XMPPBot implements AutoCloseable, ProtocolListener { } catch (XmppException vce) { logger.warn("vcard exception", vce); } - } else { - try { - String reply = juickProtocol.getReply(user, text); - Message replyMessage = new Message(message.getFrom(), Message.Type.CHAT); - replyMessage.setBody(reply); - replyMessage.setFrom(juickJid); - component.send(replyMessage); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException ex) { - logger.warn("unhandled error", ex); - } } }); component.connect(); @@ -125,42 +112,4 @@ public class XMPPBot implements AutoCloseable, ProtocolListener { logger.info("ExternalComponent on xmpp-bot destroyed"); } - - @Override - public void privateMessage(User from, User to, String body) { - List toJids = userService.getJIDsbyUID(to.getUid()); - toJids.forEach(jid -> { - Message mm = new Message(); - mm.setTo(Jid.of(jid)); - mm.setType(Message.Type.CHAT); - boolean haveInRoster = pmQueriesService.havePMinRoster(from.getUid(), jid); - if (haveInRoster) { - mm.setFrom(Jid.of(from.getName(), juickJid.getDomain(), "Juick")); - mm.setBody(body); - } else { - mm.setFrom(Jid.of("juick", juickJid.getDomain(), "Juick")); - mm.setBody("Private message from @" + from.getName() + ":\n" + body); - } - component.send(mm); - }); - } - - @Override - public void userSubscribed(User from, User to) { - String notification = String.format("%s subscribed to your blog", from.getName()); - List toJids = userService.getJIDsbyUID(to.getUid()); - toJids.forEach(jid -> { - Message mm = new Message(); - mm.setTo(Jid.of(jid)); - mm.setType(Message.Type.CHAT); - mm.setFrom(juickJid); - mm.setBody(notification); - component.send(mm); - }); - } - - @Override - public void messagePosted(com.juick.Message msg) { - - } } diff --git a/juick-xmpp/src/main/java/com/juick/components/JuickBot.java b/juick-xmpp/src/main/java/com/juick/components/JuickBot.java index 8a82063a..15e1fe66 100644 --- a/juick-xmpp/src/main/java/com/juick/components/JuickBot.java +++ b/juick-xmpp/src/main/java/com/juick/components/JuickBot.java @@ -17,11 +17,16 @@ package com.juick.components; +import com.juick.Tag; import com.juick.User; import com.juick.components.s2s.StanzaListener; import com.juick.formatters.PlainTextFormatter; +import com.juick.server.helpers.TagStats; +import com.juick.server.protocol.annotation.UserCommand; import com.juick.service.*; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.reflect.MethodUtils; import org.ocpsoft.prettytime.PrettyTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,9 +40,9 @@ import rocks.xmpp.core.stanza.model.errors.Condition; import javax.annotation.PostConstruct; import javax.inject.Inject; -import java.util.Collections; -import java.util.List; -import java.util.Locale; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -61,15 +66,19 @@ public class JuickBot implements StanzaListener, AutoCloseable { private PrettyTime pt; @Inject - public MessagesService messagesService; + private MessagesService messagesService; @Inject - public UserService userService; + private UserService userService; @Inject - public TagService tagService; + private TagService tagService; @Inject - public PMQueriesService pmQueriesService; + private PMQueriesService pmQueriesService; @Inject - public ShowQueriesService showQueriesService; + private ShowQueriesService showQueriesService; + @Inject + private PrivacyQueriesService privacyQueriesService; + @Inject + private SubscriptionService subscriptionService; @PostConstruct public void init() { @@ -82,40 +91,6 @@ public class JuickBot implements StanzaListener, AutoCloseable { return jid; } - private static final String HELPTEXT = - "@username text - Send private message\n" - + "*tagname Blah-blah-blah - Post a message with tag 'tagname'\n" - + "#1234 Blah-blah-blah - Answer to message #1234\n" - + "#1234/5 Blah - Answer to reply #1234/5\n" - + "! #1234 - Recommend post\n" - + "\n" - + "# - Show last messages from your feed (## - second page, ...)\n" - + "@ - Show recomendations and popular personal blogs\n" - + "* - Show your tags\n" - + "#1234 - Show message\n" - + "#1234+ - Show message with replies\n" - + "@username - Show user's info\n" - + "@username+ - Show user's info and last 10 messages\n" - + "@username *tag - User's messages with this tag\n" - + "*tag - Show last 10 messages with this tag\n" - + "? blah - Search posts for 'blah'\n" - + "? @username blah - Searching among user\'s posts for 'blah'\n" - + "D #123 - Delete message\n" - + "D #123/45 - Delete reply\n" - + "DL - Delete last message/reply\n" - + "S - Show your subscriptions\n" - + "S #123 - Subscribe to message replies\n" - + "S @username - Subscribe to user's blog\n" - + "U #123 - Unsubscribe from comments\n" - + "U @username - Unsubscribe from user's blog\n" - + "BL - Show your blacklist\n" - + "BL @username - Add/delete user to/from your blacklist\n" - + "BL *tag - Add/delete tag to/from your blacklist\n" - + "ON / OFF - Enable/disable subscriptions delivery\n" - + "PING - Pong\n" - + "\n" - + "Read more: http://juick.com/help/"; - public boolean incomingPresence(Presence p) { final String username = p.getTo().getLocal(); final boolean toJuick = username.equals(jid.getLocal()); @@ -245,7 +220,11 @@ public class JuickBot implements StanzaListener, AutoCloseable { } if (username.equals(jid.getLocal())) { - return incomingMessageJuick(user_from, msg); + try { + return incomingMessageJuick(user_from, msg); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + return false; + } } int uid_to = userService.getUIDbyName(username); @@ -308,67 +287,69 @@ public class JuickBot implements StanzaListener, AutoCloseable { return false; } - private static Pattern regexPM = Pattern.compile("^\\@(\\S+)\\s+([\\s\\S]+)$"); - public boolean incomingMessageJuick(User user_from, Message msg) { + public Optional processCommand(User user, Jid from, String input) throws InvocationTargetException, + IllegalAccessException, NoSuchMethodException { + Optional cmd = MethodUtils.getMethodsListWithAnnotation(getClass(), UserCommand.class).stream() + .filter(m -> Pattern.compile(m.getAnnotation(UserCommand.class).pattern(), + m.getAnnotation(UserCommand.class).patternFlags()).matcher(input).matches()) + .findFirst(); + if (cmd.isPresent()) { + Matcher matcher = Pattern.compile(cmd.get().getAnnotation(UserCommand.class).pattern(), + cmd.get().getAnnotation(UserCommand.class).patternFlags()).matcher(input); + List groups = new ArrayList<>(); + while (matcher.find()) { + for (int i = 1; i <= matcher.groupCount(); i++) { + groups.add(matcher.group(i)); + } + } + return Optional.of((String) getClass().getMethod(cmd.get().getName(), User.class, Jid.class, String[].class) + .invoke(this, user, from, groups.toArray(new String[groups.size()]))); + } + return Optional.empty(); + } + public boolean incomingMessageJuick(User user_from, Message msg) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { String command = msg.getBody().trim(); int commandlen = command.length(); // COMPATIBILITY if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) { command = command.substring(3).trim(); - commandlen = command.length(); } - if (commandlen == 4) { - if (command.equalsIgnoreCase("PING")) { - commandPing(msg); - return true; - } else if (command.equalsIgnoreCase("HELP")) { - commandHelp(msg); - return true; - } - } else if (commandlen == 5 && command.equalsIgnoreCase("LOGIN")) { - commandLogin(msg, user_from); - return true; - } else if (command.charAt(0) == '@') { - Matcher matchPM = regexPM.matcher(command); - if (matchPM.find()) { - String user_to = matchPM.group(1); - String msgtxt = matchPM.group(2); - commandPM(msg, user_from, user_to, msgtxt); - return true; - } - } else if (commandlen == 2 && command.equalsIgnoreCase("BL")) { - commandBLShow(msg, user_from); - return true; - } else if (commandlen == 2 && command.equalsIgnoreCase("#+")) { - return commandLast(msg.getFrom()); - } else if (command.equalsIgnoreCase("@")) { - return commandUsers(msg.getFrom()); - } - - return false; + Optional result = processCommand(user_from, msg.getFrom(), command); + result.ifPresent(r -> sendReply(msg.getFrom(), r)); + return result.isPresent(); } - private void commandPing(Message m) { - Presence p = new Presence(m.getFrom()); + @UserCommand(pattern = "^ping$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "PING - returns you a PONG") + public String commandPing(User user, Jid from, String[] input) { + Presence p = new Presence(from); p.setFrom(jid); p.setPriority((byte) 10); xmpp.sendOut(ClientPresence.from(p)); - - sendReply(m.getFrom(), "PONG"); + return "PONG"; } - private void commandHelp(Message m) { - sendReply(m.getFrom(), HELPTEXT); + @UserCommand(pattern = "^help$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "HELP - returns this help message") + public String commandHelp(User user, Jid from, String[] input) { + return Arrays.stream(getClass().getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(UserCommand.class)) + .map(m -> m.getAnnotation(UserCommand.class).help()) + .collect(Collectors.joining("\n")); } - private void commandLogin(Message m, User user_from) { - sendReply(m.getFrom(), "http://juick.com/login?hash=" + userService.getHashByUID(user_from.getUid())); + @UserCommand(pattern = "^login$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "LOGIN - log in to Juick website") + public String commandLogin(User user_from, Jid from, String[] input) { + return "http://juick.com/login?hash=" + userService.getHashByUID(user_from.getUid()); } - - private void commandPM(Message m, User user_from, String user_to, String body) { + @UserCommand(pattern = "^\\@(\\S+)\\s+([\\s\\S]+)$", help = "@username message - send PM to username") + public String commandPM(User user_from, Jid from, String... arguments) { + String user_to = arguments[0]; + String body = arguments[1]; int ret = 0; int uid_to = 0; @@ -425,20 +406,15 @@ public class JuickBot implements StanzaListener, AutoCloseable { } } - Message reply = new Message(); - reply.setFrom(m.getTo()); - reply.setTo(m.getFrom()); if (ret == 200) { - reply.setType(m.getType()); - reply.setBody("Private message sent"); + return "Private message sent"; } else { - reply.setType(Message.Type.ERROR); - reply.setBody("Error " + ret); + return "Error " + ret; } - xmpp.sendOut(ClientMessage.from(reply)); } - - private void commandBLShow(Message m, User user_from) { + @UserCommand(pattern = "^bl$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "BL - Show your blacklist") + public String commandBLShow(User user_from, Jid from, String... arguments) { List blusers = userService.getUserBLUsers(user_from.getUid()); List bltags = tagService.getUserBLTags(user_from.getUid()); @@ -461,21 +437,20 @@ public class JuickBot implements StanzaListener, AutoCloseable { txt = "You don't have any users or tags in your blacklist."; } - sendReply(m.getFrom(), txt); + return txt; } - boolean commandLast(Jid jidFrom) { - User user = userService.getUserByJID(jidFrom.asBareJid().toEscapedString()); - sendReply(jidFrom, "Last messages:\n" - + printMessages(messagesService.getAll(user.getUid(), 0), true)); - return true; + @UserCommand(pattern = "^#\\+$", help = "#+ - Show last Juick messages") + public String commandLast(User user_from, Jid from, String... arguments) { + return "Last messages:\n" + + printMessages(messagesService.getAll(user_from.getUid(), 0), true); } - boolean commandUsers(Jid jidFrom) { + @UserCommand(pattern = "@", help = "@ - Show recommendations and popular personal blogs") + public String commandUsers(User user_from, Jid from, String... arguments) { StringBuilder msg = new StringBuilder(); msg.append("Recommended blogs"); - User currentUser = userService.getUserByJID(jidFrom.asBareJid().toEscapedString()); - List recommendedUsers = showQueriesService.getRecommendedUsers(currentUser); + List recommendedUsers = showQueriesService.getRecommendedUsers(user_from); if (recommendedUsers.size() > 0) { for (String user : recommendedUsers) { msg.append("\n@").append(user); @@ -492,8 +467,139 @@ public class JuickBot implements StanzaListener, AutoCloseable { } else { msg.append("\nNo top users. Empty DB? ;)"); } - sendReply(jidFrom, msg.toString()); - return true; + return msg.toString(); + } + @UserCommand(pattern = "^bl\\s+@([^\\s\\n\\+]+)", patternFlags = Pattern.CASE_INSENSITIVE, + help = "BL @username - add @username to your blacklist") + public String blacklistUser(User user_from, Jid from, String... arguments) { + User blUser = userService.getUserByName(arguments[0]); + if (blUser != null) { + PrivacyQueriesService.PrivacyResult result = privacyQueriesService.blacklistUser(user_from, blUser); + if (result == PrivacyQueriesService.PrivacyResult.Added) { + return "User added to your blacklist"; + } else { + return "User removed from your blacklist"; + } + } + return "User not found"; + } + @UserCommand(pattern = "^bl\\s\\*(\\S+)$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "BL *tag - add *tag to your blacklist") + public String blacklistTag(User user_from, Jid from, String... arguments) { + User blUser = userService.getUserByName(arguments[0]); + if (blUser != null) { + Tag tag = tagService.getTag(arguments[0], false); + if (tag != null) { + PrivacyQueriesService.PrivacyResult result = privacyQueriesService.blacklistTag(user_from, tag); + if (result == PrivacyQueriesService.PrivacyResult.Added) { + return "Tag added to your blacklist"; + } else { + return "Tag removed from your blacklist"; + } + } + } + return "Tag not found"; + } + @UserCommand(pattern = "\\*", help = "* - Show your tags") + public String commandTags(User currentUser, Jid from, String... args) { + List tags = tagService.getUserTagStats(currentUser.getUid()); + String msg = "Your tags: (tag - messages)\n" + + tags.stream() + .map(t -> String.format("\n*%s - %d", t.getTag().getName(), t.getUsageCount())).collect(Collectors.joining()); + return msg; + } + @UserCommand(pattern = "S", help = "S - Show your subscriptions") + public String commandSubscriptions(User currentUser, Jid from, String... args) { + List friends = userService.getUserFriends(currentUser.getUid()); + List tags = subscriptionService.getSubscribedTags(currentUser); + String msg = friends.size() > 0 ? "You are subscribed to users:" + friends.stream().map(u -> "\n@" + u.getName()) + .collect(Collectors.joining()) + : "You are not subscribed to any user."; + msg += tags.size() > 0 ? "\nYou are subscribed to tags:" + tags.stream().map(t -> "\n*" + t) + .collect(Collectors.joining()) + : "\nYou are not subscribed to any tag."; + return msg; + } + @UserCommand(pattern = "!", help = "! - Show your favorite messages") + public String commandFavorites(User currentUser, Jid from, String... args) { + List mids = messagesService.getUserRecommendations(currentUser.getUid(), 0); + if (mids.size() > 0) { + List messages = messagesService.getMessages(mids); + return "Favorite messages: \n" + String.join("\n", messages.stream() + .sorted(Collections.reverseOrder()) + .map(PlainTextFormatter::formatPost) + .collect(Collectors.toList())); + } + return "No favorite messages, try to \"like\" something ;)"; + } + @UserCommand(pattern = "^(s|u)\\s+\\*(\\S+)$", help = "S *tag - subscribe to tag" + + "\nU *tag - unsubscribe from tag", patternFlags = Pattern.CASE_INSENSITIVE) + public String commandSubscribeTag(User user, Jid from, String... args) { + boolean subscribe = args[0].equalsIgnoreCase("s"); + Tag tag = tagService.getTag(args[1], true); + if (subscribe) { + if (subscriptionService.subscribeTag(user, tag)) { + return "Subscribed"; + } + } else { + if (subscriptionService.unSubscribeTag(user, tag)) { + return "Unsubscribed from " + tag.getName(); + } + return "You was not subscribed to " + tag.getName(); + } + return "Error"; + } + @UserCommand(pattern = "^(s|u)\\s+#(\\d+)$", help = "S #1234 - subscribe to comments", + patternFlags = Pattern.CASE_INSENSITIVE) + public String commandSubscribeMessage(User user, String... args) { + boolean subscribe = args[0].equalsIgnoreCase("s"); + int mid = NumberUtils.toInt(args[1], 0); + if (messagesService.getMessage(mid) != null) { + if (subscribe) { + if (subscriptionService.subscribeMessage(mid, user.getUid())) { + return "Subscribed"; + } + } else { + if (subscriptionService.unSubscribeMessage(mid, user.getUid())) { + return "Unsubscribed from #" + mid; + } + return "You was not subscribed to #" + mid; + } + } + return "Error"; + } + @UserCommand(pattern = "^(on|off)$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "ON/OFF - Enable/disable subscriptions delivery") + public String commandOnOff(User user, String[] input) { + UserService.ActiveStatus newStatus; + String retValUpdated; + if (input[0].toLowerCase().equals("on")) { + newStatus = UserService.ActiveStatus.Active; + retValUpdated = "Notifications are activated for " + user.getJid(); + } else { + newStatus = UserService.ActiveStatus.Inactive; + retValUpdated = "Notifications are disabled for " + user.getJid(); + } + + if (userService.setActiveStatusForJID(user.getJid(), newStatus)) { + return retValUpdated; + } else { + return String.format("Subscriptions status for %s was not changed", user.getJid()); + } + } + @UserCommand(pattern = "^\\@([^\\s\\n\\+]+)(\\+?)$", + help = "@username+ - Show user's info and last 10 messages (@username++ - second page, ..)") + public String commandUser(User user, String... arguments) { + User blogUser = userService.getUserByName(arguments[0]); + int page = arguments[1].length(); + if (blogUser.getUid() > 0) { + List mids = messagesService.getUserBlog(blogUser.getUid(), 0, page); + List messages = messagesService.getMessages(mids); + return String.format("Last messages from @%s:\n%s", arguments[0], + String.join("\n", messages.stream() + .map(PlainTextFormatter::formatPost).collect(Collectors.toList()))); + } + return "User not found"; } void sendReply(Jid jidTo, String txt) { diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java b/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java index 6a796307..8157e046 100644 --- a/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java +++ b/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java @@ -17,6 +17,7 @@ package com.juick.components.s2s; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.juick.components.XMPPServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,6 +128,7 @@ public class Connection { writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8); } + @JsonIgnore public Socket getSocket() { return socket; } diff --git a/juick-xmpp/src/test/java/com/juick/xmpp/server/XMPPServerTests.java b/juick-xmpp/src/test/java/com/juick/xmpp/server/XMPPServerTests.java index ccc4f1d4..11ca75cf 100644 --- a/juick-xmpp/src/test/java/com/juick/xmpp/server/XMPPServerTests.java +++ b/juick-xmpp/src/test/java/com/juick/xmpp/server/XMPPServerTests.java @@ -1,5 +1,6 @@ package com.juick.xmpp.server; +import com.juick.User; import com.juick.components.JuickBot; import com.juick.components.XMPPServer; import com.juick.components.configuration.XmppAppConfiguration; @@ -18,16 +19,15 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.stanza.model.Message; import rocks.xmpp.core.stanza.model.Stanza; -import rocks.xmpp.core.stanza.model.StanzaError; -import rocks.xmpp.core.stanza.model.errors.Condition; import rocks.xmpp.core.stanza.model.server.ServerMessage; import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -70,4 +70,10 @@ public class XMPPServerTests { Stanza msg = server.parse(xmlMessage); bot.incomingMessage((ServerMessage)msg); } + @Test + public void botCommandsTests() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + assertThat(bot.processCommand(new User(), Jid.of("test@localhost"), "PING").get(), is("PONG")); + // tag help have two lines, others have 1 + assertThat(bot.processCommand(new User(), Jid.of("test@localhost"), "help").get().split("\n").length, is(17)); + } } -- cgit v1.2.3