readers = ImageIO.getImageReaders(iis);
+
+ String format = StringUtils.EMPTY;
+ while (readers.hasNext()) {
+ ImageReader read = readers.next();
+ format = read.getFormatName();
+ }
+ URLConnection urlConn;
+ try {
+ urlConn = url.openConnection();
+ } catch (IOException e) {
+ logger.error(String.format("Failed open url: %s", url.toString()));
+ throw e;
+ }
+
+ try (InputStream is = new BufferedInputStream(urlConn.getInputStream())) {
+ String attachmentType = attachmentTypeFromFormat(format);
+
+ String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType;
+ Files.copy(is, Paths.get(tmpDir, attachmentFName));
+ return URI.create(String.format("juick://%s", attachmentFName));
+ } catch (IOException e) {
+ logger.error(String.format("Failed download image by url: %s", url.toString()), e);
+ throw e;
+ }
+ }
+}
diff --git a/src/main/java/com/juick/util/ImageUtils.java b/src/main/java/com/juick/util/ImageUtils.java
new file mode 100644
index 00000000..38e1de08
--- /dev/null
+++ b/src/main/java/com/juick/util/ImageUtils.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008-2020, 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.util;
+
+import com.juick.model.Attachment;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.Imaging;
+import org.apache.commons.imaging.common.ImageMetadata;
+import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
+import org.apache.commons.imaging.formats.tiff.TiffField;
+import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.imgscalr.Scalr;
+import org.imgscalr.Scalr.Rotation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Iterator;
+
+public class ImageUtils {
+ private static final Logger logger = LoggerFactory.getLogger(ImageUtils.class);
+
+ private String imgDir;
+ private String tmpDir;
+
+ public ImageUtils(String imgDir, String tmpDir) {
+ this.imgDir = imgDir;
+ this.tmpDir = tmpDir;
+ }
+/**
+ * Returns BufferedImage
, same as ImageIO.read()
does.
+ *
+ * JPEG images with EXIF metadata are rotated according to Orientation tag.
+ *
+ * @param imageFile a File
to read from.
+ */
+ private static BufferedImage readImageWithOrientation(File imageFile)
+ throws IOException {
+
+ BufferedImage image = ImageIO.read(imageFile);
+ if (!FilenameUtils.getExtension(imageFile.getName()).equals("jpg")) {
+ return image;
+ }
+
+ try {
+ ImageMetadata metadata = Imaging.getMetadata(imageFile);
+
+ if (metadata instanceof JpegImageMetadata) {
+ JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
+ TiffField orientationField = jpegMetadata.findEXIFValue(TiffTagConstants.TIFF_TAG_ORIENTATION);
+
+ if (orientationField != null) {
+ int orientation = orientationField.getIntValue();
+ switch (orientation) {
+ case TiffTagConstants.ORIENTATION_VALUE_ROTATE_90_CW:
+ image = Scalr.rotate(image, Rotation.CW_90);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_ROTATE_180:
+ image = Scalr.rotate(image, Rotation.CW_180);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW:
+ image = Scalr.rotate(image, Rotation.CW_270);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL:
+ image = Scalr.rotate(image, Rotation.FLIP_HORZ);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_VERTICAL:
+ image = Scalr.rotate(image, Rotation.FLIP_VERT);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW:
+ image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_90);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW:
+ image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_270);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_HORIZONTAL_NORMAL:
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+ } catch (ImageReadException | IOException e) {
+ // failed to read metadata.
+ // nothing to do here, return image as is.
+ }
+
+ return image;
+ }
+
+ public void saveImageWithPreviews(String tempFilename, String outputFilename)
+ throws IOException {
+ String ext = FilenameUtils.getExtension(outputFilename);
+
+ Path outputImagePath = Paths.get(imgDir, "p", outputFilename);
+ // this throws strange exceptions
+ // Files.move(Paths.get(tmpDir, tempFilename), outputImagePath);
+ FileUtils.moveFile(Paths.get(tmpDir, tempFilename).toFile(), outputImagePath.toFile());
+ BufferedImage originalImage = readImageWithOrientation(outputImagePath.toFile());
+
+ int width = originalImage.getWidth();
+ int height = originalImage.getHeight();
+ int maxDimension = Math.max(width, height);
+ BufferedImage image1024 = (maxDimension > 1024) ? Scalr.resize(originalImage, 1024) : originalImage;
+ BufferedImage image0512 = (maxDimension > 512) ? Scalr.resize(originalImage, 512) : originalImage;
+ BufferedImage image0160 = (maxDimension > 160) ? Scalr.resize(originalImage, 160) : originalImage;
+ ImageIO.write(image1024, ext, Paths.get(imgDir, "photos-1024", outputFilename).toFile());
+ ImageIO.write(image0512, ext, Paths.get(imgDir, "photos-512", outputFilename).toFile());
+ ImageIO.write(image0160, ext, Paths.get(imgDir, "ps", outputFilename).toFile());
+ }
+
+ public void saveAvatar(String tempFilename, int uid)
+ throws IOException {
+ String ext = FilenameUtils.getExtension(tempFilename);
+ String originalName = String.format("%s.%s", uid, ext);
+ Path originalPath = Paths.get(imgDir, "ao", originalName);
+ Files.move(Paths.get(tmpDir, tempFilename), originalPath, StandardCopyOption.REPLACE_EXISTING);
+ BufferedImage originalImage = ImageIO.read(originalPath.toFile());
+
+ String targetExt = "png";
+ String targetName = String.format("%s.%s", uid, targetExt);
+ ImageIO.write(Scalr.resize(originalImage, 96), targetExt, Paths.get(imgDir, "a", targetName).toFile());
+ ImageIO.write(Scalr.resize(originalImage, 32), targetExt, Paths.get(imgDir, "as", targetName).toFile());
+ }
+ public Attachment getAttachment(File imgFile) throws IOException {
+ Attachment attachment = new Attachment();
+ try (ImageInputStream stream = ImageIO.createImageInputStream(imgFile)) {
+ Iterator iter = ImageIO.getImageReaders(stream);
+ while (iter.hasNext()) {
+ ImageReader reader = iter.next();
+ try {
+ reader.setInput(stream);
+ attachment.setWidth(reader.getWidth(reader.getMinIndex()));
+ attachment.setHeight(reader.getHeight(reader.getMinIndex()));
+ return attachment;
+ } catch (Exception e) {
+ logger.debug("Error reading {}, trying next reader", imgFile.getAbsolutePath());
+ } finally {
+ reader.dispose();
+ }
+ }
+ }
+
+ logger.warn("Not a known image file {}", imgFile.getAbsolutePath());
+ return attachment;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/juick/util/TagUtils.java b/src/main/java/com/juick/util/TagUtils.java
new file mode 100644
index 00000000..2ec03e48
--- /dev/null
+++ b/src/main/java/com/juick/util/TagUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2008-2020, 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.util;
+
+import com.juick.model.Tag;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Created by aalexeev on 11/13/16.
+ */
+public class TagUtils {
+ private TagUtils() {
+ throw new IllegalStateException();
+ }
+
+ public static String toString(final List tags) {
+ if (CollectionUtils.isEmpty(tags))
+ return StringUtils.EMPTY;
+
+ return tags.stream().map(t -> "*" + t.getName())
+ .collect(Collectors.joining(" "));
+ }
+}
diff --git a/src/main/java/com/juick/util/WebUtils.java b/src/main/java/com/juick/util/WebUtils.java
new file mode 100644
index 00000000..3a8c7620
--- /dev/null
+++ b/src/main/java/com/juick/util/WebUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008-2020, 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.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+/**
+ * Created by aalexeev on 11/28/16.
+ */
+public class WebUtils {
+ private WebUtils() {
+ throw new IllegalStateException();
+ }
+
+ private static final Pattern USER_NAME_PATTERN = Pattern.compile("[a-zA-Z-_\\d]{2,16}");
+
+ private static final Pattern POST_NUMBER_PATTERN = Pattern.compile("-?\\d+");
+
+ private static final Pattern JID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\\\-\\\\_\\\\@\\\\.]{6,64}$");
+
+
+ public static boolean isPostNumber(final String aString) {
+ return aString != null && POST_NUMBER_PATTERN.matcher(aString).matches();
+ }
+
+ public static boolean isNotPostNumber(final String aString) {
+ return !isPostNumber(aString);
+ }
+
+ public static boolean isUserName(final String aString) {
+ return aString != null && USER_NAME_PATTERN.matcher(aString).matches();
+ }
+
+ public static boolean isNotUserName(final String aString) {
+ return !isUserName(aString);
+ }
+
+ public static boolean isJid(final String aString) {
+ return aString != null && JID_PATTERN.matcher(aString).matches();
+ }
+
+ public static boolean isNotJid(final String aString) {
+ return !isJid(aString);
+ }
+
+ public static String encodeSphinx(String str) {
+ return str.replaceAll("@", "\\\\@")
+ .replaceAll("\\'", "\\\\'")
+ .replaceAll("=", "\\\\\\\\=");
+ }
+ /**
+ * Returns the viewName to return for coming back to the sender url
+ *
+ * @param request Instance of {@link HttpServletRequest} or use an injected instance
+ * @return Optional with the view name. Recomended to use an alternativa url with
+ * {@link Optional#orElse(java.lang.Object)}
+ */
+ public static Optional getPreviousPageByRequest(HttpServletRequest request)
+ {
+ return Optional.ofNullable(request.getHeader("Referer"));
+ }
+}
diff --git a/src/main/java/com/juick/util/adapters/SimpleDateAdapter.java b/src/main/java/com/juick/util/adapters/SimpleDateAdapter.java
new file mode 100644
index 00000000..fd5f3b5a
--- /dev/null
+++ b/src/main/java/com/juick/util/adapters/SimpleDateAdapter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008-2019, 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.util.adapters;
+
+import com.juick.util.DateFormattersHolder;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import java.time.Instant;
+
+/**
+ * Created by vitalyster on 15.11.2016.
+ */
+
+public class SimpleDateAdapter extends XmlAdapter {
+
+ @Override
+ public String marshal(Instant v) {
+ return DateFormattersHolder.getMessageFormatterInstance().format(v);
+ }
+
+ @Override
+ public Instant unmarshal(String v) {
+ return DateFormattersHolder.getMessageFormatterInstance().parse(v);
+ }
+}
diff --git a/src/main/java/com/juick/util/annotation/UserCommand.java b/src/main/java/com/juick/util/annotation/UserCommand.java
new file mode 100644
index 00000000..29e40ca2
--- /dev/null
+++ b/src/main/java/com/juick/util/annotation/UserCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008-2019, 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.util.annotation;
+
+import org.apache.commons.lang3.StringUtils;
+
+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 StringUtils.EMPTY;
+
+ /**
+ *
+ * @return pattern flags
+ */
+ int patternFlags() default 0;
+
+ /**
+ *
+ * @return a string used in HELP command output. Basically, only 1 string
+ */
+ String help() default StringUtils.EMPTY;
+}
diff --git a/src/main/java/com/juick/util/formatters/PlainTextFormatter.java b/src/main/java/com/juick/util/formatters/PlainTextFormatter.java
new file mode 100644
index 00000000..b5d67030
--- /dev/null
+++ b/src/main/java/com/juick/util/formatters/PlainTextFormatter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008-2019, 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.util.formatters;
+
+import com.juick.model.Message;
+import com.juick.util.MessageUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.ocpsoft.prettytime.PrettyTime;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Created by vitalyster on 12.10.2016.
+ */
+public class PlainTextFormatter {
+ static PrettyTime pt = new PrettyTime(new Locale("en"));
+
+ public static String formatPost(Message jmsg) {
+ return formatPost(jmsg, false);
+ }
+
+ public static String formatPost(Message jmsg, boolean markdown) {
+ StringBuilder sb = new StringBuilder();
+ String title = MessageUtils.isReply(jmsg) ? "Reply by @" : MessageUtils.isPM(jmsg) ? "Private message from @" : "@";
+ String subtitle = MessageUtils.isReply(jmsg) ? markdown ? MessageUtils.escapeMarkdown(StringUtils.defaultString(jmsg.getReplyQuote()))
+ : jmsg.getReplyQuote()
+ : markdown ? MessageUtils.getMessageHashTags(jmsg) : MessageUtils.getTagsString(jmsg);
+ sb.append(title).append(markdown ? MessageUtils.getMarkdownUser(jmsg.getUser()) : jmsg.getUser().getName()).append(":\n")
+ .append(subtitle).append("\n");
+ if (markdown) {
+ sb.append(MessageUtils.formatMarkdownText(jmsg));
+ } else {
+ sb.append(StringUtils.defaultString(jmsg.getText()));
+ }
+ sb.append("\n");
+ if (!markdown && StringUtils.isNotEmpty(jmsg.getAttachmentType())) {
+ sb.append(MessageUtils.attachmentUrl(jmsg));
+ }
+ return sb.toString();
+ }
+
+ public static String formatPostSummary(Message m) {
+ int cropLength = 384;
+ String timeAgo = pt.format(Date.from(m.getCreated()));
+ String repliesCount = m.getReplies() == 1 ? "; 1 reply" : m.getReplies() == 0 ? ""
+ : String.format("; %d replies", m.getReplies());
+ StringBuilder sb = new StringBuilder();
+ String txt = StringUtils.defaultString(m.getText());
+ String attachmentUrl = MessageUtils.attachmentUrl(m);
+ if (StringUtils.isNotEmpty(attachmentUrl)) {
+ sb.append(attachmentUrl).append("\n");
+ }
+ if (txt.length() >= cropLength) {
+ sb.append(StringUtils.substring(txt, 0, cropLength)).append(" [...]");
+ } else {
+ sb.append(txt);
+ }
+ return String.format("@%s:%s\n%s\n#%s (%s%s) %s",
+ m.getUser().getName(), MessageUtils.getTagsString(m), sb.toString(), formatPostNumber(m), timeAgo, repliesCount, formatUrl(m));
+ }
+
+ public static String formatUrl(Message jmsg) {
+ if (MessageUtils.isReply(jmsg)) {
+ return String.format("https://juick.com/m/%d#%d", jmsg.getMid(), jmsg.getRid());
+ } else if (MessageUtils.isPM(jmsg)) {
+ return "https://juick.com/pm/inbox";
+ }
+ return "https://juick.com/m/" + jmsg.getMid();
+ }
+
+ public static String markdownUrl(String url, String description) {
+ if (StringUtils.isNotBlank(description)) {
+ return String.format("[%s](%s)", description, url);
+ } else {
+ return url;
+ }
+ }
+
+ public static String formatPostNumber(Message jmsg) {
+ if (jmsg.getRid() > 0) {
+ return String.format("%d/%d", jmsg.getMid(), jmsg.getRid());
+ }
+ return String.format("%d", jmsg.getMid());
+ }
+
+ public static String formatTwitterCard(Message jmsg) {
+ return MessageUtils.getMessageHashTags(jmsg) + StringUtils.defaultString(jmsg.getText());
+ }
+}
diff --git a/src/main/java/com/juick/util/xmpp/JidConverter.java b/src/main/java/com/juick/util/xmpp/JidConverter.java
new file mode 100644
index 00000000..118c6711
--- /dev/null
+++ b/src/main/java/com/juick/util/xmpp/JidConverter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008-2019, 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.util.xmpp;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.lang.Nullable;
+import rocks.xmpp.addr.Jid;
+
+import javax.annotation.Nonnull;
+
+public class JidConverter implements Converter {
+ @Nullable
+ @Override
+ public Jid convert(@Nonnull String jidStr) {
+ return Jid.of(jidStr);
+ }
+}
diff --git a/src/main/java/com/juick/util/xmpp/iq/MessageQuery.java b/src/main/java/com/juick/util/xmpp/iq/MessageQuery.java
new file mode 100644
index 00000000..c1096e8e
--- /dev/null
+++ b/src/main/java/com/juick/util/xmpp/iq/MessageQuery.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008-2019, 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.util.xmpp.iq;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "query")
+public class MessageQuery {
+ private MessageQuery() {
+
+ }
+}
diff --git a/src/main/java/com/juick/util/xmpp/iq/package-info.java b/src/main/java/com/juick/util/xmpp/iq/package-info.java
new file mode 100644
index 00000000..7a1694a5
--- /dev/null
+++ b/src/main/java/com/juick/util/xmpp/iq/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2008-2019, 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 .
+ */
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlSchema(namespace = "http://juick.com/query#messages", elementFormDefault = XmlNsForm.QUALIFIED)
+package com.juick.util.xmpp.iq;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlNsForm;
+import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
--
cgit v1.2.3