package com.juick.components; import com.juick.User; import com.juick.server.helpers.UserInfo; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import rocks.xmpp.addr.Jid; import rocks.xmpp.core.XmppException; import rocks.xmpp.core.stanza.AbstractIQHandler; import rocks.xmpp.core.stanza.model.IQ; import rocks.xmpp.core.stanza.model.Message; import rocks.xmpp.core.stanza.model.Stanza; import rocks.xmpp.core.stanza.model.client.ClientIQ; import rocks.xmpp.core.stanza.model.client.ClientMessage; import rocks.xmpp.core.stanza.model.errors.Condition; 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.receipts.MessageDeliveryReceiptsManager; import rocks.xmpp.extensions.vcard.temp.model.VCard; import rocks.xmpp.util.XmppUtils; import javax.annotation.PostConstruct; 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.concurrent.ExecutionException; /** * @author ugnich */ public class XMPPConnection implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(XMPPConnection.class); private ExternalComponent router; @Inject private XMPPServer xmpp; @Inject private Environment env; @PostConstruct public void init() { logger.info("stream router start"); String componentName = env.getProperty("componentname"); int componentPort = NumberUtils.toInt(env.getProperty("component_port"), 5347); String password = env.getProperty("xmpp_password"); router = ExternalComponent.create(componentName, password, xmpp.getSession().getConfiguration(), "localhost", componentPort); PingManager pingManager = router.getManager(PingManager.class); pingManager.setEnabled(true); MessageDeliveryReceiptsManager messageDeliveryReceiptsManager = router.getManager(MessageDeliveryReceiptsManager.class); messageDeliveryReceiptsManager.setEnabled(true); 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(xmpp.getJid()) || iq.getTo().asBareJid().equals(xmpp.getJid().asBareJid()) || iq.getTo().asBareJid().toEscapedString().equals(xmpp.getJid().getDomain())) { return iq.createResult(vCard); } User user = xmpp.userService.getUserByName(iq.getTo().getLocal()); if (user.getUid() > 0) { UserInfo info = xmpp.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(Jid.of(componentName).getDomain())) { com.juick.Message jmsg = message.getExtension(com.juick.Message.class); if (jmsg != null) { if (jid.getLocal().equals("recomm")) { sendJuickRecommendation(jmsg); } else { if (jmsg.getRid() > 0) { sendJuickComment(jmsg); } else if (jmsg.getMid() > 0) { sendJuickMessage(jmsg); } } } } else if (jid.getDomain().endsWith(xmpp.HOSTNAME) && (jid.getDomain().equals(xmpp.HOSTNAME) || jid.getDomain().endsWith("." + xmpp.HOSTNAME))) { if (logger.isInfoEnabled()) { try { logger.info("unhandled message: {}", stanzaToString(message)); } catch (JAXBException | XMLStreamException ex) { logger.error("JAXB exception", ex); } } } else { route(jid.getDomain(), ClientMessage.from(message)); } }); router.addInboundIQListener(e -> { IQ iq = e.getIQ(); Jid jid = iq.getTo(); if (!jid.getDomain().equals(xmpp.HOSTNAME)) { route(jid.getDomain(), iq); } }); String tmpDir = env.getProperty("upload_tmp_dir", "/tmp"); 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 (ft.isDone()) { logger.info("transfer completed"); Message msg = new Message(); msg.setType(Message.Type.CHAT); msg.setFrom(e.getInitiator()); msg.setTo(xmpp.getJid()); msg.setBody(e.getDescription()); try { String attachmentUrl = String.format("juick://%s", targetFilename); msg.addExtension(new OobX(new URI(attachmentUrl), "!!!!Juick!!")); router.sendMessage(msg); } catch (URISyntaxException e1) { logger.warn("attachment error", e1); } } }); ft.transfer(); logger.info("transfer started"); } else { e.reject(); logger.info("transfer rejected"); } } catch (IOException | InterruptedException | ExecutionException e1) { logger.error("ft error", e1); } }); try { router.connect(); } catch (XmppException e) { logger.warn("xmpp exception", e); } } 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(); } void route(String domain, Stanza stanza) { try { String xml = stanzaToString(stanza); logger.info("stream router (out): {}", xml); xmpp.sendOut(domain, xml); } catch (XMLStreamException | JAXBException e) { logger.error("JAXB exception", e); } } public void sendStanza(Stanza xml) { router.send(xml); } public void sendJuickMessage(com.juick.Message jmsg) { List jids = new ArrayList<>(); if (jmsg.FriendsOnly) { jids = xmpp.subscriptionService.getJIDSubscribedToUser(jmsg.getUser().getUid(), jmsg.FriendsOnly); } else { List users = xmpp.subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid()); for (User user : users) { for (String jid : xmpp.userService.getJIDsbyUID(user.getUid())) { jids.add(jid); } } } com.juick.Message fullMsg = xmpp.messagesService.getMessage(jmsg.getMid()); String txt = "@" + jmsg.getUser().getName() + ":" + fullMsg.getTagsString() + "\n"; String attachment = fullMsg.getAttachmentURL(); if (attachment != null) { txt += attachment + "\n"; } txt += 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(xmpp.getJid()); msg.setBody(txt); msg.setType(Message.Type.CHAT); msg.setThread("juick-" + jmsg.getMid()); msg.addExtension(jmsg); msg.addExtension(nick); if (attachment != null) { try { OobX oob = new OobX(new URI(attachment)); msg.addExtension(oob); } catch (URISyntaxException e) { logger.warn("uri exception", e); } } for (String jid : jids) { msg.setTo(Jid.of(jid)); route(msg.getTo().getDomain(), ClientMessage.from(msg)); } } public void sendJuickComment(com.juick.Message jmsg) { List users; String replyQuote; String replyTo; users = xmpp.subscriptionService.getUsersSubscribedToComments(jmsg.getMid(), jmsg.getUser().getUid()); com.juick.Message replyMessage = jmsg.getReplyto() > 0 ? xmpp.messagesService.getReply(jmsg.getMid(), jmsg.getReplyto()) : xmpp.messagesService.getMessage(jmsg.getMid()); replyTo = replyMessage.getUser().getName(); com.juick.Message fullReply = xmpp.messagesService.getReply(jmsg.getMid(), jmsg.getRid()); replyQuote = fullReply.getReplyQuote(); String txt = "Reply by @" + jmsg.getUser().getName() + ":\n" + replyQuote + "\n@" + replyTo + " "; String attachment = jmsg.getAttachmentURL(); if (attachment != null) { txt += attachment + "\n"; } txt += jmsg.getText() + "\n\n" + "#" + jmsg.getMid() + "/" + jmsg.getRid() + " http://juick.com/" + jmsg.getMid() + "#" + jmsg.getRid(); Message msg = new Message(); msg.setFrom(xmpp.getJid()); msg.setBody(txt); msg.setType(Message.Type.CHAT); msg.addExtension(jmsg); for (User user : users) { for (String jid : xmpp.userService.getJIDsbyUID(user.getUid())) { msg.setTo(Jid.of(jid)); route(msg.getTo().getDomain(), ClientMessage.from(msg)); } } } public void sendJuickRecommendation(com.juick.Message recomm) { List users; com.juick.Message jmsg = xmpp.messagesService.getMessage(recomm.getMid()); users = xmpp.subscriptionService.getUsersSubscribedToUserRecommendations(recomm.getUser().getUid(), recomm.getMid(), jmsg.getUser().getUid()); String txt = "Recommended by @" + recomm.getUser().getName() + ":\n"; txt += "@" + jmsg.getUser().getName() + ":" + jmsg.getTagsString() + "\n"; String attachment = jmsg.getAttachmentURL(); if (attachment != null) { txt += attachment + "\n"; } txt += 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(xmpp.getJid()); msg.setBody(txt); msg.setType(Message.Type.CHAT); msg.setThread("juick-" + jmsg.getMid()); msg.addExtension(jmsg); msg.addExtension(nick); if (attachment != null) { try { OobX oob = new OobX(new URI(attachment)); msg.addExtension(oob); } catch (URISyntaxException e) { logger.warn("uri exception", e); } } for (User user : users) { for (String jid : xmpp.userService.getJIDsbyUID(user.getUid())) { msg.setTo(Jid.of(jid)); route(msg.getTo().getDomain(), ClientMessage.from(msg)); } } } @Override public void close() throws Exception { if (router != null) { router.close(); } } }