diff options
author | Vitaly Takmazov | 2017-10-30 13:52:55 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2017-10-30 23:55:25 +0300 |
commit | 240a375ecdeb7592d8765f0edb7d2fd67c50ac10 (patch) | |
tree | 7d518988a1fc3c79ccb00cd07fdb4201569c674e /juick-xmpp | |
parent | 3ca4188155e5f04a5cc96c4be20ef206e9f3ffde (diff) |
xmpp: bot refactoring
Diffstat (limited to 'juick-xmpp')
3 files changed, 223 insertions, 109 deletions
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<String> processCommand(User user, Jid from, String input) throws InvocationTargetException, + IllegalAccessException, NoSuchMethodException { + Optional<Method> 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<String> 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<String> 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<User> blusers = userService.getUserBLUsers(user_from.getUid()); List<String> 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<String> recommendedUsers = showQueriesService.getRecommendedUsers(currentUser); + List<String> 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<TagStats> 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<User> friends = userService.getUserFriends(currentUser.getUid()); + List<String> 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<Integer> mids = messagesService.getUserRecommendations(currentUser.getUid(), 0); + if (mids.size() > 0) { + List<com.juick.Message> 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<Integer> mids = messagesService.getUserBlog(blogUser.getUid(), 0, page); + List<com.juick.Message> 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)); + } } |