diff options
Diffstat (limited to 'juick-server/src/main/java/com/juick/server/protocol')
3 files changed, 512 insertions, 0 deletions
diff --git a/juick-server/src/main/java/com/juick/server/protocol/JuickProtocol.java b/juick-server/src/main/java/com/juick/server/protocol/JuickProtocol.java new file mode 100644 index 00000000..6fa3790e --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/protocol/JuickProtocol.java @@ -0,0 +1,458 @@ +package com.juick.server.protocol; + +import com.juick.Message; +import com.juick.Tag; +import com.juick.User; +import com.juick.formatters.PlainTextFormatter; +import com.juick.json.MessageSerializer; +import com.juick.server.*; +import com.juick.server.protocol.annotation.UserCommand; +import com.juick.util.TagUtils; +import org.springframework.jdbc.core.JdbcTemplate; + +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; + +/** + * Created by oxpa on 22.03.16. + */ + +public class JuickProtocol { + MessageSerializer json = new MessageSerializer(); + JdbcTemplate sql; + String baseUri; + + public JuickProtocol(JdbcTemplate sql, String baseUri) { + this.sql = sql; + this.baseUri = baseUri; + } + + /** + * find command by pattern and invoke + * @param user who send command + * @param userInput given by user + * @return command result + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + */ + public ProtocolReply getReply(User user, String userInput) throws InvocationTargetException, + IllegalAccessException, NoSuchMethodException { + Optional<Method> cmd = Arrays.stream(getClass().getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(UserCommand.class)) + .filter(m -> Pattern.compile(m.getAnnotation(UserCommand.class).pattern(), + m.getAnnotation(UserCommand.class).patternFlags()).matcher(userInput).matches()) + .findFirst(); + if (!cmd.isPresent()) { + // default command - post as new message + return postMessage(user, userInput); + } else { + Matcher matcher = Pattern.compile(cmd.get().getAnnotation(UserCommand.class).pattern(), + cmd.get().getAnnotation(UserCommand.class).patternFlags()).matcher(userInput); + List<String> groups = new ArrayList<>(); + while (matcher.find()) { + for (int i = 1; i <= matcher.groupCount(); i++) { + groups.add(matcher.group(i)); + } + } + return (ProtocolReply) getClass().getMethod(cmd.get().getName(), User.class, String[].class) + .invoke(this, user, groups.toArray(new String[groups.size()])); + } + } + + public ProtocolReply postMessage(User user, String input) { + List<Tag> tags = TagQueries.fromString(sql, input, false); + String body = input.substring(TagUtils.toString(tags).length()); + int mid = MessagesQueries.createMessage(sql, user.getUid(), body, null, tags); + SubscriptionsQueries.subscribeMessage(sql, mid, user.getUid()); + //app.events().publishEvent(new JuickMessageEvent(app.messages().getMessage(mid))); + return new ProtocolReply("New message posted.\n#" + mid + " " + baseUri + mid, + Optional.of(json.serializeList(Collections.singletonList(MessagesQueries.getMessage(sql, mid))))); + } + + @UserCommand(pattern = "^#(\\++)$", help = "#+ - Show last Juick messages (#++ - second page, ...)") + public ProtocolReply commandLast(User user, String... arguments) { + // number of + is the page count + int page = arguments[0].length() - 1; + List<Integer> mids = MessagesQueries.getAll(sql, user.getUid(), page); + List<Message> messages = MessagesQueries.getMessages(sql, mids); + return new ProtocolReply("Last messages: \n" + String.join("\n", messages.stream().map(PlainTextFormatter::formatPost) + .collect(Collectors.toList())), Optional.of(json.serializeList(messages))); + } + + @UserCommand(pattern = "^\\s*bl\\s*$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "BL - Show your blacklist") + public ProtocolReply commandBL(User user_from, String... arguments) { + List<User> blusers; + List<String> bltags; + + blusers = UserQueries.getUserBLUsers(sql, user_from.getUid()); + bltags = TagQueries.getUserBLTags(sql, user_from.getUid()); + + + String txt = ""; + 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 new ProtocolReply(txt, Optional.empty()); + } + + @UserCommand(pattern = "^bl\\s+@([^\\s\\n\\+]+)", patternFlags = Pattern.CASE_INSENSITIVE, + help = "BL @username - add @username to your blacklist") + public ProtocolReply blacklistUser(User from, String... arguments) { + User blUser = UserQueries.getUserByName(sql, arguments[0]); + if (blUser != null) { + PrivacyQueries.PrivacyResult result = PrivacyQueries.blacklistUser(sql, from, blUser); + if (result == PrivacyQueries.PrivacyResult.Added) { + return new ProtocolReply("User added to your blacklist", Optional.empty()); + } else { + return new ProtocolReply("User removed from your blacklist", Optional.empty()); + } + } + return new ProtocolReply("User not found", Optional.empty()); + } + + @UserCommand(pattern = "^bl\\s\\*(\\S+)$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "BL *tag - add *tag to your blacklist") + public ProtocolReply blacklistTag(User from, String... arguments) { + User blUser = UserQueries.getUserByName(sql, arguments[0]); + if (blUser != null) { + Tag tag = TagQueries.getTag(sql, arguments[0], false); + if (tag != null) { + PrivacyQueries.PrivacyResult result = PrivacyQueries.blacklistTag(sql, from, tag); + if (result == PrivacyQueries.PrivacyResult.Added) { + return new ProtocolReply("Tag added to your blacklist", Optional.empty()); + } else { + return new ProtocolReply("Tag removed from your blacklist", Optional.empty()); + } + } + } + return new ProtocolReply("Tag not found", Optional.empty()); + } + + @UserCommand(pattern = "@", help = "@ - Show recommendations and popular personal blogs") + public ProtocolReply commandUsers(User currentUser, String... args) { + StringBuilder msg = new StringBuilder(); + msg.append("Recommended blogs"); + List<String> recommendedUsers = ShowQueries.getRecommendedUsers(sql, 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<String> topUsers = ShowQueries.getTopUsers(sql); + if (topUsers.size() > 0) { + for (String user : topUsers) { + msg.append("\n@").append(user); + } + } else { + msg.append("\nNo top users. Empty DB? ;)"); + } + return new ProtocolReply(msg.toString(), Optional.empty()); + } + + @UserCommand(pattern = "\\*", help = "* - Show your tags") + public ProtocolReply commandTags(User currentUser, String... args) { + List<Tag> tags = TagQueries.getUserTagsAll(sql, currentUser.getUid()); + String msg = "Your tags: (tag - messages)\n" + + tags.stream() + .map(t -> String.format("\n*%s - %d", t.getName(), t.UsageCnt)).collect(Collectors.joining()); + return new ProtocolReply(msg, Optional.empty()); + } + + @UserCommand(pattern = "!", help = "! - Show your favorite messages") + public ProtocolReply commandFavorites(User currentUser, String... args) { + List<Integer> mids = MessagesQueries.getUserRecommendations(sql, currentUser.getUid(), 0); + if (mids.size() > 0) { + List<Message> messages = MessagesQueries.getMessages(sql, mids); + return new ProtocolReply("Favorite messages: \n" + String.join("\n", messages.stream().map(PlainTextFormatter::formatPost) + .collect(Collectors.toList())), Optional.of(json.serializeList(messages))); + } + return new ProtocolReply("No favorite messages, try to \"like\" something ;)", Optional.empty()); + } + + @UserCommand(pattern = "^\\@([^\\s\\n\\+]+)(\\+?)$", + help = "@username+ - Show user's info and last 10 messages (@username++ - second page, ..)") + public ProtocolReply commandUser(User user, String... arguments) { + User blogUser = UserQueries.getUserByName(sql, arguments[0]); + int page = arguments[1].length(); + if (blogUser != null) { + List<Integer> mids = MessagesQueries.getUserBlog(sql, blogUser.getUid(), 0, page); + List<Message> messages = MessagesQueries.getMessages(sql, mids); + return new ProtocolReply(String.format("Last messages from @%s:\n%s", arguments[0], + String.join("\n", messages.stream() + .map(Object::toString).collect(Collectors.toList()))), + Optional.of(json.serializeList(messages))); + } + return new ProtocolReply("User not found", Optional.empty()); + } + + @UserCommand(pattern = "^\\s*d\\s*\\#([0-9]+)\\s*$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "D #12345 - delete the message") + public ProtocolReply commandDel(User user, String... args) { + try { + int mid = Integer.parseInt(args[0]); + if (MessagesQueries.deleteMessage(sql, user.getUid(), mid)) { + return new ProtocolReply(String.format("Message %s deleted", mid), Optional.empty()); + } + } catch (NumberFormatException e) { + return new ProtocolReply("Error", Optional.empty()); + } + return new ProtocolReply("Error", Optional.empty()); + } + + @UserCommand(pattern = "^\\s*login\\s*$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "LOGIN - log in to Juick website") + public ProtocolReply commandLogin(User user, String... arguments) { + return new ProtocolReply(baseUri + "?" + UserQueries.getHashByUID(sql, user.getUid()), + Optional.empty()); + } + + @UserCommand(pattern = "^(#+)$", help = "# - Show last messages from your feed (## - second page, ...)") + public ProtocolReply commandMyFeed(User user, String... arguments) { + // number of # is the page count + int page = arguments[0].length() - 1; + List<Integer> mids = MessagesQueries.getMyFeed(sql, user.getUid(), page); + List<Message> messages = MessagesQueries.getMessages(sql, mids); + // TODO: add instructions for empty feed + return new ProtocolReply("Your feed: \n" + String.join("\n", + messages.stream().map(PlainTextFormatter::formatPost).collect(Collectors.toList())), + Optional.of(json.serializeList(messages))); + } + + @UserCommand(pattern = "^\\s*(on|off)\\s*$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "ON/OFF - Enable/disable subscriptions delivery") + public ProtocolReply commandOnOff(User user, String[] input) { + UserQueries.ActiveStatus newStatus; + String retValUpdated; + if (input[0].toLowerCase().equals("on")) { + newStatus = UserQueries.ActiveStatus.Active; + retValUpdated = "Notifications are activated for " + user.getJID(); + } else { + newStatus = UserQueries.ActiveStatus.Inactive; + retValUpdated = "Notifications are disabled for " + user.getJID(); + } + + if (UserQueries.setActiveStatusForJID(sql, user.getJID(), newStatus)) { + return new ProtocolReply(retValUpdated, Optional.empty()); + } else { + return new ProtocolReply(String.format("Subscriptions status for %s was not changed", user.getJID()), + Optional.empty()); + } + } + + @UserCommand(pattern = "^\\s*ping\\s*$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "PING - returns you a PONG") + public ProtocolReply commandPing(User user, String[] input) { + return new ProtocolReply("PONG", Optional.empty()); + } + + @UserCommand(pattern = "^\\@(\\S+)\\s+([\\s\\S]+)$", help = "@username message - send PM to username") + public ProtocolReply commandPM(User user_from, String... arguments) { + String user_to = arguments[0]; + String body = arguments[1]; + int ret = 0; + + int uid_to = 0; + String jid_to = null; + boolean haveInRoster = false; + + if (user_to.indexOf('@') > 0) { + uid_to = UserQueries.getUIDbyJID(sql, user_to); + } else { + uid_to = UserQueries.getUIDbyName(sql, user_to); + } + + if (uid_to > 0) { + if (!UserQueries.isInBLAny(sql, uid_to, user_from.getUid())) { + if (PMQueries.createPM(sql, user_from.getUid(), uid_to, body)) { + //jid_to = UserQueries.getJIDsbyUID(sql, uid_to); + if (jid_to != null) { + haveInRoster = PMQueries.havePMinRoster(sql, user_from.getUid(), jid_to); + } + ret = 200; + } else { + ret = 500; + } + } else { + ret = 403; + } + } else { + ret = 404; + } + + + if (ret == 200) { + Message jmsg = new Message(); + jmsg.setUser(user_from); + jmsg.setText(body); + // TODO: add PM payload + //app.events().publishEvent(new JuickMessageEvent(jmsg)); + /* TODO: move to XMPP component + if (jid_to != null) { + Message mm = new Message(); + mm.to = new JID(jid_to); + mm.type = Message.Type.chat; + if (haveInRoster) { + mm.from = new JID(user_from.getName(), getDomain(), "Juick"); + mm.body = body; + } else { + mm.from = new JID("juick", getDomain(), "Juick"); + mm.body = "Private message from @" + user_from.getName() + ":\n" + body; + } + return Collections.singletonList(mm); + } + */ + } + if (ret == 200) { + return new ProtocolReply("Private message sent", Optional.empty()); + } else { + return new ProtocolReply("Error " + ret, Optional.empty()); + } + } + + @UserCommand(pattern = "^#(\\d+)(\\+?)$", help = "#1234 - Show message (#1234+ - message with replies)") + public ProtocolReply commandShow(User user, String... arguments) { + boolean showReplies = arguments[1].length() > 0; + int mid; + try { + mid = Integer.parseInt(arguments[0]); + } catch (NumberFormatException e) { + return new ProtocolReply("Error", Optional.empty()); + } + Message msg = MessagesQueries.getMessage(sql, mid); + if (msg != null) { + if (showReplies) { + List<Message> replies = MessagesQueries.getReplies(sql, mid); + replies.add(0, msg); + return new ProtocolReply(String.join("\n", + replies.stream().map(PlainTextFormatter::formatPost).collect(Collectors.toList())), + Optional.of(json.serializeList(replies))); + } + return new ProtocolReply(PlainTextFormatter.formatPost(msg), Optional.of(json.serializeList(Collections.singletonList(msg)))); + } + return new ProtocolReply("Message not found", Optional.empty()); + } + @UserCommand(pattern = "^(#|\\.)(\\d+)((\\.|\\-|\\/)(\\d+))?\\s([\\s\\S]+)", + help = "#1234 *tag *tag2 - edit tags\n#1234 text - reply to message") + public ProtocolReply EditOrReply(User user, String... args) { + int mid; + try { + mid = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + return new ProtocolReply("Error", Optional.empty()); + } + int rid; + try { + rid = Integer.parseInt(args[4]); + } catch (NumberFormatException e) { + rid = 0; + } + String txt = args[5]; + List<Tag> messageTags = TagQueries.fromString(sql, txt, true); + if (messageTags.size() > 0) { + if (user.getUid() != MessagesQueries.getMessageAuthor(sql, mid).getUid()) { + return new ProtocolReply("It is not your message", Optional.empty()); + } + TagQueries.updateTags(sql, mid, messageTags); + return new ProtocolReply("Tags are updated", Optional.empty()); + } else { + int newrid = MessagesQueries.createReply(sql, mid, rid, user.getUid(), txt, null); + return new ProtocolReply("Reply posted.\n#" + mid + "/" + newrid + " " + + baseUri + mid + "/" + newrid, + Optional.of(json.serializeList(Collections.singletonList(MessagesQueries.getReply(sql, mid, newrid))))); + } + } + + @UserCommand(pattern = "^(s|u)\\s+#(\\d+)$", help = "S #1234 - subscribe to comments", + patternFlags = Pattern.CASE_INSENSITIVE) + public ProtocolReply commandSubscribeMessage(User user, String... args) { + boolean subscribe = args[0].equalsIgnoreCase("s"); + int mid; + try { + mid = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + return new ProtocolReply("Error", Optional.empty()); + } + if (subscribe) { + if (SubscriptionsQueries.subscribeMessage(sql, mid, user.getUid())) { + return new ProtocolReply("Subscribed", Optional.empty()); + } + } else { + if (SubscriptionsQueries.unSubscribeMessage(sql, mid, user.getUid())) { + return new ProtocolReply("Unsubscribed from #" + mid, Optional.empty()); + } + return new ProtocolReply("You was not subscribed to #" + mid, Optional.empty()); + } + return new ProtocolReply("Error", Optional.empty()); + } + @UserCommand(pattern = "^(s|u)\\s+\\@(\\S+)$", help = "S @user - subscribe to user's posts", + patternFlags = Pattern.CASE_INSENSITIVE) + public ProtocolReply commandSubscribeUser(User user, String... args) { + boolean subscribe = args[0].equalsIgnoreCase("s"); + User toUser = UserQueries.getUserByName(sql, args[1]); + if (toUser.getUid() > 0) { + if (subscribe) { + if (SubscriptionsQueries.subscribeUser(sql, user, toUser)) { + return new ProtocolReply("Subscribed", Optional.empty()); + // TODO: notification + // TODO: already subscribed case + } + } else { + if (SubscriptionsQueries.unSubscribeUser(sql, user, toUser)) { + return new ProtocolReply("Unsubscribed from @" + toUser.getName(), Optional.empty()); + } + return new ProtocolReply("You was not subscribed to @" + toUser.getName(), Optional.empty()); + } + } + return new ProtocolReply("Error", Optional.empty()); + } + @UserCommand(pattern = "^(s|u)\\s+\\*(\\S+)$", help = "S *tag - subscribe to tag" + + "\nU *tag - unsubscribe from tag", patternFlags = Pattern.CASE_INSENSITIVE) + public ProtocolReply commandSubscribeTag(User user, String... args) { + boolean subscribe = args[0].equalsIgnoreCase("s"); + Tag tag = TagQueries.getTag(sql, args[1], true); + if (subscribe) { + if (SubscriptionsQueries.subscribeTag(sql, user, tag)) { + return new ProtocolReply("Subscribed", Optional.empty()); + } + } else { + if (SubscriptionsQueries.unSubscribeTag(sql, user, tag)) { + return new ProtocolReply("Unsubscribed from " + tag.getName(), Optional.empty()); + } + return new ProtocolReply("You was not subscribed to " + tag.getName(), Optional.empty()); + } + return new ProtocolReply("Error", Optional.empty()); + } + + @UserCommand(pattern = "^\\s*help\\s*$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "HELP - returns this help message") + public ProtocolReply commandHelp(User user, String[] input) { + List<String> commandsHelp = Arrays.stream(getClass().getDeclaredMethods()) + .filter(m -> m.isAnnotationPresent(UserCommand.class)) + .map(m -> m.getAnnotation(UserCommand.class).help()) + .collect(Collectors.toList()); + return new ProtocolReply(String.join("\n", commandsHelp), Optional.empty()); + } +} diff --git a/juick-server/src/main/java/com/juick/server/protocol/ProtocolReply.java b/juick-server/src/main/java/com/juick/server/protocol/ProtocolReply.java new file mode 100644 index 00000000..d9d36a5d --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/protocol/ProtocolReply.java @@ -0,0 +1,23 @@ +package com.juick.server.protocol; + +import java.util.Optional; + +/** + * Created by vitalyster on 08.04.2016. + */ +public class ProtocolReply { + + private Optional<String> json; + private String description; + + public ProtocolReply(String text, Optional<String> json) { + this.description = text; + this.json = json; + } + public String getDescription() { + return description; + } + public Optional<String> getJson() { + return json; + } +} diff --git a/juick-server/src/main/java/com/juick/server/protocol/annotation/UserCommand.java b/juick-server/src/main/java/com/juick/server/protocol/annotation/UserCommand.java new file mode 100644 index 00000000..af7c4924 --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/protocol/annotation/UserCommand.java @@ -0,0 +1,31 @@ +package com.juick.server.protocol.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created by oxpa on 22.03.16. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface UserCommand { + /** + * + * @return a command pattern + */ + String pattern() default ""; + + /** + * + * @return pattern flags + */ + int patternFlags() default 0; + + /** + * + * @return a string used in HELP command output. Basically, only 1 string + */ + String help() default ""; +} |