From 5b5ca32a22e2e8e95c9bca86ce23d19c4a69f83d Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 2 Apr 2018 16:47:02 +0300 Subject: xmpp: move to library project --- .../main/java/com/juick/server/XMPPConnection.java | 750 +++++++++++++++++++++ 1 file changed, 750 insertions(+) create mode 100644 juick-server-xmpp/src/main/java/com/juick/server/XMPPConnection.java (limited to 'juick-server-xmpp/src/main/java/com/juick/server/XMPPConnection.java') diff --git a/juick-server-xmpp/src/main/java/com/juick/server/XMPPConnection.java b/juick-server-xmpp/src/main/java/com/juick/server/XMPPConnection.java new file mode 100644 index 00000000..a60b506d --- /dev/null +++ b/juick-server-xmpp/src/main/java/com/juick/server/XMPPConnection.java @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.server; + +import com.juick.Tag; +import com.juick.User; +import com.juick.server.component.LikeEvent; +import com.juick.server.component.MessageEvent; +import com.juick.server.component.PingEvent; +import com.juick.server.component.SubscribeEvent; +import com.juick.server.helpers.UserInfo; +import com.juick.server.util.HttpUtils; +import com.juick.server.util.ImageUtils; +import com.juick.server.util.TagUtils; +import com.juick.server.xmpp.helpers.CommandResult; +import com.juick.server.xmpp.s2s.BasicXmppSession; +import com.juick.server.xmpp.s2s.StanzaListener; +import com.juick.service.*; +import com.juick.util.MessageUtils; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; +import rocks.xmpp.addr.Jid; +import rocks.xmpp.core.XmppException; +import rocks.xmpp.core.stanza.AbstractIQHandler; +import rocks.xmpp.core.stanza.model.*; +import rocks.xmpp.core.stanza.model.client.ClientMessage; +import rocks.xmpp.core.stanza.model.client.ClientPresence; +import rocks.xmpp.core.stanza.model.errors.Condition; +import rocks.xmpp.extensions.caps.model.EntityCapabilities; +import rocks.xmpp.extensions.component.accept.ExternalComponent; +import rocks.xmpp.extensions.filetransfer.FileTransfer; +import rocks.xmpp.extensions.filetransfer.FileTransferManager; +import rocks.xmpp.extensions.nick.model.Nickname; +import rocks.xmpp.extensions.oob.model.x.OobX; +import rocks.xmpp.extensions.ping.PingManager; +import rocks.xmpp.extensions.vcard.temp.model.VCard; +import rocks.xmpp.extensions.version.SoftwareVersionManager; +import rocks.xmpp.extensions.version.model.SoftwareVersion; +import rocks.xmpp.util.XmppUtils; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +/** + * @author ugnich + */ +@Component +@DependsOn("XMPPRouter") +public class XMPPConnection implements StanzaListener, NotificationListener { + + private static final Logger logger = LoggerFactory.getLogger(XMPPConnection.class); + + private ExternalComponent router; + @Inject + private XMPPServer xmpp; + @Inject + private CommandsManager commandsManager; + @Value("${xmppbot_jid:juick@localhost}") + private Jid jid; + @Value("${componentname:localhost}") + private String componentName; + @Value("${component_port:5347}") + private int componentPort; + @Value("${xmpp_password:secret}") + private String password; + @Value("${xmpp_disabled:false}") + private boolean isXmppDisabled; + @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") + private String tmpDir; + @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") + private String imgDir; + + @Inject + private MessagesService messagesService; + @Inject + private UserService userService; + @Inject + private SubscriptionService subscriptionService; + @Inject + private PMQueriesService pmQueriesService; + @Inject + private TagService tagService; + @Inject + private BasicXmppSession session; + @Inject + private ExecutorService service; + @Inject + private ApplicationEventPublisher applicationEventPublisher; + + @PostConstruct + public void init() { + logger.info("stream router start connecting to {}", componentPort); + xmpp.addStanzaListener(this); + router = ExternalComponent.create(componentName, password, session.getConfiguration(), "localhost", + componentPort); + PingManager pingManager = router.getManager(PingManager.class); + pingManager.setEnabled(true); + router.disableFeature(EntityCapabilities.NAMESPACE); + SoftwareVersionManager softwareVersionManager = router.getManager(SoftwareVersionManager.class); + softwareVersionManager.setSoftwareVersion(new SoftwareVersion("Juick", "2.x", + System.getProperty("os.name", "generic"))); + VCard vCard = new VCard(); + vCard.setFormattedName("Juick"); + try { + vCard.setUrl(new URL("http://juick.com/")); + vCard.setPhoto(new VCard.Image("image/png", IOUtils.toByteArray( + getClass().getClassLoader().getResource("juick.png")))); + } catch (MalformedURLException e) { + logger.error("invalid url", e); + } catch (IOException e) { + logger.warn("invalid resource", e); + } + router.addIQHandler(VCard.class, new AbstractIQHandler(IQ.Type.GET) { + @Override + protected IQ processRequest(IQ iq) { + if (iq.getTo().equals(jid) || iq.getTo().asBareJid().equals(jid.asBareJid()) + || iq.getTo().asBareJid().toEscapedString().equals(jid.getDomain())) { + return iq.createResult(vCard); + } + User user = userService.getUserByName(iq.getTo().getLocal()); + if (user.getUid() > 0) { + UserInfo info = userService.getUserInfo(user); + VCard userVCard = new VCard(); + userVCard.setFormattedName(info.getFullName()); + userVCard.setNickname(user.getName()); + try { + userVCard.setPhoto(new VCard.Image(new URI("http://i.juick.com/a/" + user.getUid() + ".png"))); + if (info.getUrl() != null) { + userVCard.setUrl(new URL(info.getUrl())); + } + } catch (MalformedURLException | URISyntaxException e) { + logger.warn("url exception", e); + } + return iq.createResult(userVCard); + } + return iq.createError(Condition.BAD_REQUEST); + } + }); + router.addInboundMessageListener(e -> { + Message message = e.getMessage(); + Jid jid = message.getTo(); + if (jid.getDomain().equals(router.getDomain().toEscapedString()) || jid.equals(jid)) { + com.juick.Message jmsg = message.getExtension(com.juick.Message.class); + if (jmsg != null) { + if (jid.getLocal().equals("recomm")) { + User fromUser = jmsg.getUser(); + com.juick.Message msg = messagesService.getMessage(jmsg.getMid()); + applicationEventPublisher.publishEvent(new LikeEvent(this, fromUser, msg)); + } else if (jid.getLocal().equals("pm")) { + applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg)); + } else { + if (jmsg.getRid() > 0) { + // to get quote and attachment + com.juick.Message reply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); + sendJuickComment(reply); + applicationEventPublisher.publishEvent(new MessageEvent(this, reply)); + } else if (jmsg.getMid() > 0) { + sendJuickMessage(jmsg); + applicationEventPublisher.publishEvent(new MessageEvent(this, + messagesService.getMessage(jmsg.getMid()))); + } + } + } else { + String attachment = StringUtils.EMPTY; + OobX oobX = message.getExtension(OobX.class); + if (oobX != null) { + attachment = oobX.getUri().toString(); + } + try { + processMessage(userService.getUserByUID(NumberUtils.toInt(message.getFrom().getLocal(), 0)).orElse(new User()), message.getBody(), attachment); + } catch (Exception e1) { + logger.warn("message exception", e1); + } + } + } else if (jid.getDomain().endsWith(jid.getDomain()) && (jid.getDomain().equals(this.jid.getDomain()) + || jid.getDomain().endsWith("." + jid.getDomain()))) { + if (logger.isInfoEnabled()) { + try { + logger.info("unhandled message: {}", stanzaToString(message)); + } catch (JAXBException | XMLStreamException ex) { + logger.error("JAXB exception", ex); + } + } + } else { + s2s(ClientMessage.from(message)); + } + }); + router.addInboundIQListener(e -> { + IQ iq = e.getIQ(); + Jid jid = iq.getTo(); + if (!jid.getDomain().equals(this.jid.getDomain())) { + s2s(iq); + } + }); + FileTransferManager fileTransferManager = router.getManager(FileTransferManager.class); + fileTransferManager.addFileTransferOfferListener(e -> { + try { + List allowedTypes = new ArrayList() {{ + add("png"); + add("jpg"); + }}; + String attachmentExtension = FilenameUtils.getExtension(e.getName()).toLowerCase(); + String targetFilename = String.format("%s.%s", + DigestUtils.md5Hex(String.format("%s-%s", + e.getInitiator().toString(), e.getSessionId()).getBytes()), attachmentExtension); + if (allowedTypes.contains(attachmentExtension)) { + Path filePath = Paths.get(tmpDir, targetFilename); + FileTransfer ft = e.accept(filePath).get(); + ft.addFileTransferStatusListener(st -> { + logger.debug("{}: received {} of {}", e.getName(), st.getBytesTransferred(), e.getSize()); + if (st.getStatus().equals(FileTransfer.Status.COMPLETED)) { + logger.info("transfer completed"); + try { + processMessage(userService.getUserByJID(e.getInitiator().toEscapedString()), e.getDescription(), targetFilename); + } catch (Exception e1) { + logger.error("ft error", e1); + } + + } else if (st.getStatus().equals(FileTransfer.Status.FAILED)) { + logger.info("transfer failed", ft.getException()); + Message msg = new Message(); + msg.setType(Message.Type.CHAT); + msg.setFrom(jid); + msg.setTo(e.getInitiator()); + msg.setBody("File transfer failed, please report to us"); + router.sendMessage(msg); + } else if (st.getStatus().equals(FileTransfer.Status.CANCELED)) { + logger.info("transfer cancelled"); + } + }); + ft.transfer(); + logger.info("transfer started"); + } else { + e.reject(); + logger.info("transfer rejected"); + } + } catch (IOException | InterruptedException | ExecutionException e1) { + logger.error("ft error", e1); + } + }); + router.addConnectionListener(event -> { + if (event.getType().equals(rocks.xmpp.core.session.ConnectionEvent.Type.RECONNECTION_SUCCEEDED)) { + logger.info("component connected"); + } + }); + if (!isXmppDisabled) { + service.submit(() -> { + try { + Thread.sleep(3000); + router.connect(); + broadcastPresence(null); + } catch (InterruptedException | XmppException e) { + logger.warn("xmpp exception", e); + } + }); + } + } + + private String stanzaToString(Stanza stanza) throws XMLStreamException, JAXBException { + StringWriter stanzaWriter = new StringWriter(); + XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( + router.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); + router.createMarshaller().marshal(stanza, xmppStreamWriter); + xmppStreamWriter.flush(); + xmppStreamWriter.close(); + return stanzaWriter.toString(); + } + + private void s2s(Stanza stanza) { + try { + String xml = stanzaToString(stanza); + logger.info("stream router (out): {}", xml); + xmpp.sendOut(stanza); + } catch (XMLStreamException | JAXBException e) { + logger.error("JAXB exception", e); + } + + } + + + + private void sendJuickMessage(com.juick.Message jmsg) { + List jids = new ArrayList<>(); + + if (jmsg.FriendsOnly) { + jids = subscriptionService.getJIDSubscribedToUser(jmsg.getUser().getUid(), jmsg.FriendsOnly); + } else { + List users = subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid()); + for (User user : users) { + jids.addAll(userService.getJIDsbyUID(user.getUid())); + } + } + com.juick.Message fullMsg = messagesService.getMessage(jmsg.getMid()); + String txt = "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(fullMsg) + "\n"; + String attachmentUrl = MessageUtils.attachmentUrl(fullMsg); + if (StringUtils.isNotEmpty(attachmentUrl)) { + txt += attachmentUrl + "\n"; + } + txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; + txt += "#" + jmsg.getMid() + " http://juick.com/" + jmsg.getMid(); + + Nickname nick = new Nickname("@" + jmsg.getUser().getName()); + + Message msg = new Message(); + msg.setFrom(jid); + msg.setBody(txt); + msg.setType(Message.Type.CHAT); + msg.setThread("juick-" + jmsg.getMid()); + msg.addExtension(jmsg); + msg.addExtension(nick); + if (StringUtils.isNotEmpty(attachmentUrl)) { + try { + OobX oob = new OobX(new URI(attachmentUrl)); + msg.addExtension(oob); + } catch (URISyntaxException e) { + logger.warn("uri exception", e); + } + } + for (String jid : jids) { + msg.setTo(Jid.of(jid)); + s2s(ClientMessage.from(msg)); + } + } + + public void sendJuickComment(com.juick.Message jmsg) { + List users; + String replyQuote; + String replyTo; + + com.juick.Message op = messagesService.getMessage(jmsg.getMid()); + users = subscriptionService.getUsersSubscribedToComments(op, jmsg); + com.juick.Message replyMessage = jmsg.getReplyto() > 0 ? messagesService.getReply(jmsg.getMid(), jmsg.getReplyto()) + : messagesService.getMessage(jmsg.getMid()); + replyTo = replyMessage.getUser().getName(); + com.juick.Message fullReply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); + replyQuote = fullReply.getReplyQuote(); + + String txt = "Reply by @" + jmsg.getUser().getName() + ":\n" + replyQuote + "\n@" + replyTo + " "; + String attachmentUrl = MessageUtils.attachmentUrl(fullReply); + if (StringUtils.isNotEmpty(attachmentUrl)) { + txt += attachmentUrl + "\n"; + } + txt += StringUtils.defaultString(jmsg.getText()) + "\n\n" + "#" + jmsg.getMid() + "/" + jmsg.getRid() + " http://juick.com/" + jmsg.getMid() + "#" + jmsg.getRid(); + + Message msg = new Message(); + msg.setFrom(jid); + msg.setBody(txt); + msg.setType(Message.Type.CHAT); + msg.addExtension(jmsg); + for (User user : users) { + for (String jid : userService.getJIDsbyUID(user.getUid())) { + msg.setTo(Jid.of(jid)); + s2s(ClientMessage.from(msg)); + } + } + } + + @Override + public void processMessageEvent(MessageEvent event) { + com.juick.Message msg = event.getMessage(); + boolean isPM = msg.getMid() == 0; + boolean isReply = msg.getRid() > 0; + if (isPM) { + userService.getJIDsbyUID(msg.getTo().getUid()) + .forEach(userJid -> { + Message mm = new Message(); + mm.setTo(Jid.of(userJid)); + mm.setType(Message.Type.CHAT); + boolean inroster = pmQueriesService.havePMinRoster(msg.getUser().getUid(), userJid); + if (inroster) { + mm.setFrom(Jid.of(msg.getUser().getName(), "juick.com", "Juick")); + mm.setBody(msg.getText()); + } else { + mm.setFrom(jid); + mm.setBody("Private message from @" + msg.getUser().getName() + ":\n" + msg.getText()); + } + s2s(ClientMessage.from(mm)); + }); + } else if (!isReply) { + String notify = "New message posted.\n#" + msg.getMid() + " https://juick.com/" + msg.getMid(); + userService.getJIDsbyUID(msg.getUser().getUid()) + .forEach(jid -> sendReply(Jid.of(jid), notify)); + } + } + + private void sendReply(Jid jidTo, String txt) { + Message reply = new Message(); + reply.setFrom(jid); + reply.setTo(jidTo); + reply.setType(Message.Type.CHAT); + reply.setBody(txt); + s2s(ClientMessage.from(reply)); + } + + @Override + public void processSubscribeEvent(SubscribeEvent subscribeEvent) { + + } + + @Override + public void processLikeEvent(LikeEvent likeEvent) { + List users; + com.juick.Message jmsg = likeEvent.getMessage(); + User liker = likeEvent.getUser(); + users = subscriptionService.getUsersSubscribedToUserRecommendations(liker.getUid(), + jmsg.getMid(), jmsg.getUser().getUid()); + + String txt = "Recommended by @" + liker.getName() + ":\n"; + txt += "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(jmsg) + "\n"; + String attachmentUrl = MessageUtils.attachmentUrl(jmsg); + if (StringUtils.isNotEmpty(attachmentUrl)) { + txt += attachmentUrl + "\n"; + } + txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; + txt += "#" + jmsg.getMid(); + if (jmsg.getReplies() > 0) { + if (jmsg.getReplies() % 10 == 1 && jmsg.getReplies() % 100 != 11) { + txt += " (" + jmsg.getReplies() + " reply)"; + } else { + txt += " (" + jmsg.getReplies() + " replies)"; + } + } + txt += " http://juick.com/" + jmsg.getMid(); + + Nickname nick = new Nickname("@" + jmsg.getUser().getName()); + + Message msg = new Message(); + msg.setFrom(jid); + msg.setBody(txt); + msg.setType(Message.Type.CHAT); + msg.setThread("juick-" + jmsg.getMid()); + msg.addExtension(jmsg); + msg.addExtension(nick); + if (StringUtils.isNotEmpty(attachmentUrl)) { + try { + OobX oob = new OobX(new URI(attachmentUrl)); + msg.addExtension(oob); + } catch (URISyntaxException e) { + logger.warn("uri exception", e); + } + } + + for (User user : users) { + for (String jid : userService.getJIDsbyUID(user.getUid())) { + msg.setTo(Jid.of(jid)); + s2s(ClientMessage.from(msg)); + } + } + } + + @Override + public void ProcessPingEvent(PingEvent pingEvent) { + userService.getJIDsbyUID(pingEvent.getPinger().getUid()) + .forEach(userJid -> { + Presence p = new Presence(Jid.of(userJid)); + p.setFrom(jid); + p.setPriority((byte) 10); + s2s(ClientPresence.from(p)); + }); + } + + private void incomingPresence(Presence p) { + final String username = p.getTo().getLocal(); + final boolean toJuick = username.equals(jid.getLocal()); + + if (p.getType() == null) { + Presence reply = new Presence(); + reply.setFrom(p.getTo().asBareJid()); + reply.setTo(p.getFrom().asBareJid()); + reply.setType(Presence.Type.UNSUBSCRIBE); + s2s(ClientPresence.from(reply)); + } else if (p.getType().equals(Presence.Type.PROBE)) { + int uid_to = 0; + if (!toJuick) { + uid_to = userService.getUIDbyName(username); + } + + if (toJuick || uid_to > 0) { + Presence reply = new Presence(); + reply.setFrom(p.getTo().withResource(jid.getResource())); + reply.setTo(p.getFrom()); + reply.setPriority((byte)10); + if (!userService.getActiveJIDs().contains(p.getFrom().asBareJid().toEscapedString())) { + reply.setStatus("Send ON to enable notifications"); + } + s2s(ClientPresence.from(reply)); + } else { + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.ERROR); + reply.setId(p.getId()); + reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); + s2s(ClientPresence.from(reply)); + } + } else if (p.getType().equals(Presence.Type.SUBSCRIBE)) { + boolean canSubscribe = false; + if (toJuick) { + canSubscribe = true; + } else { + int uid_to = userService.getUIDbyName(username); + if (uid_to > 0) { + pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); + canSubscribe = true; + } + } + if (canSubscribe) { + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.SUBSCRIBED); + s2s(ClientPresence.from(reply)); + + reply.setFrom(reply.getFrom().withResource(jid.getResource())); + reply.setPriority((byte) 10); + reply.setType(null); + s2s(ClientPresence.from(reply)); + } else { + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.ERROR); + reply.setId(p.getId()); + reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); + s2s(ClientPresence.from(reply)); + } + } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) { + if (!toJuick) { + int uid_to = userService.getUIDbyName(username); + if (uid_to > 0) { + pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); + } + } + + Presence reply = new Presence(); + reply.setFrom(p.getTo()); + reply.setTo(p.getFrom()); + reply.setType(Presence.Type.UNSUBSCRIBED); + s2s(ClientPresence.from(reply)); + } + } + + public void incomingMessage(Message msg) { + if (msg.getType() != null && msg.getType().equals(Message.Type.ERROR)) { + StanzaError error = msg.getError(); + if (error != null && error.getCondition().equals(Condition.RESOURCE_CONSTRAINT)) { + // offline query is full, deactivating this jid + if (userService.setActiveStatusForJID(msg.getFrom().toEscapedString(), UserService.ActiveStatus.Inactive)) { + logger.info("{} is inactive now", msg.getFrom()); + return; + } + } + return; + } + if (StringUtils.isBlank(msg.getBody())) { + return; + } + String username = msg.getTo().getLocal(); + + User user_from; + String signuphash = StringUtils.EMPTY; + user_from = userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString()); + if (user_from == null) { + signuphash = userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString()); + } + + if (user_from == null) { + Message reply = new Message(); + reply.setFrom(msg.getTo()); + reply.setTo(msg.getFrom()); + reply.setType(Message.Type.CHAT); + reply.setBody("Для того, чтобы начать пользоваться сервисом, пожалуйста пройдите быструю регистрацию: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nTo start using Juick, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account."); + s2s(ClientMessage.from(reply)); + return; + } + + if (username.equals(jid.getLocal())) { + try { + OobX oobX = msg.getExtension(OobX.class); + if (oobX != null) { + incomingMessageJuick(user_from, msg.getFrom(), msg.getBody().trim(), oobX.getUri()); + } else { + incomingMessageJuick(user_from, msg.getFrom(), msg.getBody().trim(), null); + } + } catch (Exception e) { + return; + } + } + + int uid_to = userService.getUIDbyName(username); + + if (uid_to == 0) { + Message reply = new Message(); + reply.setFrom(msg.getTo()); + reply.setTo(msg.getFrom()); + reply.setType(Message.Type.ERROR); + reply.setId(msg.getId()); + reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); + s2s(ClientMessage.from(reply)); + return; + } + + boolean success = false; + if (!userService.isInBLAny(uid_to, user_from.getUid())) { + success = pmQueriesService.createPM(user_from.getUid(), uid_to, msg.getBody()); + } + + if (success) { + com.juick.Message jmsg = new com.juick.Message(); + jmsg.setUser(user_from); + jmsg.setTo(userService.getUserByUID(uid_to).get()); + jmsg.setText(msg.getBody()); + applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg)); + } else { + Message reply = new Message(); + reply.setFrom(msg.getTo()); + reply.setTo(msg.getFrom()); + reply.setType(Message.Type.ERROR); + reply.setId(msg.getId()); + reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.NOT_ALLOWED)); + s2s(ClientMessage.from(reply)); + } + + return; + } + public com.juick.Message incomingMessageJuick(User user_from, Jid from, String command, URI attachment) throws Exception { + int commandlen = command.length(); + + // COMPATIBILITY + if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) { + command = command.substring(3).trim(); + } + + Optional result = commandsManager.processCommand(user_from, from, command, attachment); + if (result.isPresent()) { + sendReply(from, result.get().getText()); + return result.get().getNewMessage(); + } else { + // new message + List tags = tagService.fromString(command, false); + String body = command.substring(TagUtils.toString(tags).length()); + String attachmentType = StringUtils.isNotEmpty(attachment.toString()) ? attachment.toString().substring(attachment.toString().length() - 3) : null; + int mid = messagesService.createMessage(user_from.getUid(), body, attachmentType, tags); + subscriptionService.subscribeMessage(mid, user_from.getUid()); + if (StringUtils.isNotEmpty(attachmentType)) { + String attachmentFName = attachment.getScheme().equals("juick") ? attachment.getHost() + : HttpUtils.downloadImage(attachment.toURL(), tmpDir); + String fname = String.format("%d.%s", mid, attachmentType); + ImageUtils.saveImageWithPreviews(attachmentFName, fname, tmpDir, imgDir); + } + com.juick.Message msg = messagesService.getMessage(mid); + applicationEventPublisher.publishEvent(new MessageEvent(this, msg)); + return msg; + } + } + public com.juick.Message processMessage(User visitor, String body, String attachmentName) throws Exception { + if (StringUtils.isNotEmpty(attachmentName)) { + URI httpUri = URI.create(attachmentName); + if (!httpUri.isAbsolute()) { + attachmentName = String.format("juick://%s", attachmentName); + } + } + return incomingMessageJuick(visitor, Jid.of(String.valueOf(visitor.getUid()), "uid.juick.com", "perl"), body, URI.create(attachmentName)); + } + + @Override + public void stanzaReceived(Stanza xmlValue) { + if (xmlValue instanceof Presence) { + Presence p = (Presence) xmlValue; + if (p.getType() == null || !p.getType().equals(Presence.Type.ERROR)) { + incomingPresence(p); + } + } else if (xmlValue instanceof Message) { + Message msg = (Message) xmlValue; + incomingMessage(msg); + } else if (xmlValue instanceof IQ) { + IQ iq = (IQ) xmlValue; + router.send(iq); + } + } + + private void broadcastPresence(Presence.Type type) { + Presence presence = new Presence(); + presence.setFrom(jid); + if (type != null) { + presence.setType(type); + } + userService.getActiveJIDs().forEach(j -> { + try { + presence.setTo(Jid.of(j)); + s2s(ClientPresence.from(presence)); + } catch (IllegalArgumentException ex) { + logger.warn("Invalid jid: {}", j, ex); + } + }); + } + + @PreDestroy + public void close() throws Exception { + broadcastPresence(Presence.Type.UNAVAILABLE); + if (router != null) { + router.close(); + } + } + + ExternalComponent getRouter() { + return router; + } +} -- cgit v1.2.3