diff --git a/doc/doap.xml b/doc/doap.xml
index b3d2c4e07..bd6e69012 100644
--- a/doc/doap.xml
+++ b/doc/doap.xml
@@ -622,6 +622,14 @@ SPDX-License-Identifier: CC0-1.0
1.5
+
+
+
+ complete
+ 0.1
+ 1.5
+
+
1.4.0
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 37e62c410..35e326dd9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -84,6 +84,7 @@ set(INSTALL_HEADER_FILES
base/QXmppSocks.h
base/QXmppStanza.h
base/QXmppStartTlsPacket.h
+ base/QXmppStickerPackItem.h
base/QXmppStream.h
base/QXmppStreamFeatures.h
base/QXmppStun.h
@@ -212,6 +213,7 @@ set(SOURCE_FILES
base/QXmppRpcIq.cpp
base/QXmppSasl.cpp
base/QXmppSessionIq.cpp
+ base/QXmppStickerPackItem.cpp
base/QXmppSocks.cpp
base/QXmppStanza.cpp
base/QXmppStartTlsPacket.cpp
diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp
index d0dfa3dd9..da5fc3a3d 100644
--- a/src/base/QXmppConstants.cpp
+++ b/src/base/QXmppConstants.cpp
@@ -204,5 +204,7 @@ const char *ns_file_metadata = "urn:xmpp:file:metadata:0";
const char *ns_sfs = "urn:xmpp:sfs:0";
// XEP-0448: Encryption for stateless file sharing
const char *ns_esfs = "urn:xmpp:esfs:0";
+// XEP-0449: Stickers
+const char *ns_stickers = "urn:xmpp:stickers:0";
// XEP-0450: Automatic Trust Management (ATM)
const char *ns_atm = "urn:xmpp:atm:1";
diff --git a/src/base/QXmppConstants_p.h b/src/base/QXmppConstants_p.h
index ac6c18d89..e7f7a8ec8 100644
--- a/src/base/QXmppConstants_p.h
+++ b/src/base/QXmppConstants_p.h
@@ -216,6 +216,8 @@ extern const char *ns_file_metadata;
extern const char *ns_sfs;
// XEP-0448: Encryption for stateless file sharing
extern const char *ns_esfs;
+// XEP-0449: Stickers
+extern const char *ns_stickers;
// XEP-0450: Automatic Trust Management (ATM)
extern const char *ns_atm;
diff --git a/src/base/QXmppMessage.cpp b/src/base/QXmppMessage.cpp
index 7998598c9..0d51d6ee6 100644
--- a/src/base/QXmppMessage.cpp
+++ b/src/base/QXmppMessage.cpp
@@ -156,6 +156,9 @@ class QXmppMessagePrivate : public QSharedData
// XEP-0448: Encryption for stateless file sharing
QVector sharedFiles;
+
+ // XEP-0449: Stickers
+ std::optional stickerPackId;
};
QXmppMessagePrivate::QXmppMessagePrivate()
@@ -1258,6 +1261,30 @@ void QXmppMessage::setSharedFiles(const QVector &sharedFiles)
d->sharedFiles = sharedFiles;
}
+///
+/// \brief Returns the sticker pack id for the sticker contained in this message
+///
+/// This is used for \xep{0449, Stickers}.
+///
+/// \since QXmpp 1.5
+///
+const std::optional &QXmppMessage::stickerPackId() const
+{
+ return d->stickerPackId;
+}
+
+///
+/// \brief Sets the sticker pack id for the sticker contained in this message
+///
+/// This is used for \xep{0449, Stickers}.
+///
+/// \since QXmpp 1.5
+///
+void QXmppMessage::setStickerPackId(const std::optional &stickerPackId)
+{
+ d->stickerPackId = stickerPackId;
+}
+
/// \cond
void QXmppMessage::parse(const QDomElement &element)
{
@@ -1552,6 +1579,12 @@ bool QXmppMessage::parseExtension(const QDomElement &element, QXmpp::SceMode sce
}
return true;
}
+
+ // XEP-0449: Stickers
+ if (checkElement(element, QStringLiteral("sticker"), ns_stickers)) {
+ d->stickerPackId = element.attribute("pack");
+ return true;
+ }
}
return false;
}
@@ -1804,5 +1837,13 @@ void QXmppMessage::serializeExtensions(QXmlStreamWriter *writer, QXmpp::SceMode
for (const auto &fileShare : d->sharedFiles) {
fileShare.toXml(writer);
}
+
+ // XEP-0449: Sticker
+ if (d->stickerPackId) {
+ writer->writeStartElement("sticker");
+ writer->writeDefaultNamespace(ns_stickers);
+ writer->writeAttribute("pack", *d->stickerPackId);
+ writer->writeEndElement();
+ }
}
}
diff --git a/src/base/QXmppMessage.h b/src/base/QXmppMessage.h
index 899f477ed..a0cb358c1 100644
--- a/src/base/QXmppMessage.h
+++ b/src/base/QXmppMessage.h
@@ -257,6 +257,10 @@ class QXMPP_EXPORT QXmppMessage : public QXmppStanza
const QVector &sharedFiles() const;
void setSharedFiles(const QVector &sharedFiles);
+ // XEP-0449: Stickers
+ const std::optional &stickerPackId() const;
+ void setStickerPackId(const std::optional &stickerPackId);
+
/// \cond
#ifdef BUILD_OMEMO
// XEP-0384: OMEMO Encryption
diff --git a/src/base/QXmppPubSubItem.cpp b/src/base/QXmppPubSubItem.cpp
index f87c5d6ee..fba4ea801 100644
--- a/src/base/QXmppPubSubItem.cpp
+++ b/src/base/QXmppPubSubItem.cpp
@@ -62,12 +62,12 @@ QXmppPubSubItem::QXmppPubSubItem(const QString &id, const QString &publisher)
/// Default copy-constructor
QXmppPubSubItem::QXmppPubSubItem(const QXmppPubSubItem &iq) = default;
/// Default move-constructor
-QXmppPubSubItem::QXmppPubSubItem(QXmppPubSubItem &&) = default;
+QXmppPubSubItem::QXmppPubSubItem(QXmppPubSubItem &&) noexcept = default;
QXmppPubSubItem::~QXmppPubSubItem() = default;
/// Default assignment operator
QXmppPubSubItem &QXmppPubSubItem::operator=(const QXmppPubSubItem &iq) = default;
/// Default move-assignment operator
-QXmppPubSubItem &QXmppPubSubItem::operator=(QXmppPubSubItem &&iq) = default;
+QXmppPubSubItem &QXmppPubSubItem::operator=(QXmppPubSubItem &&iq) noexcept = default;
///
/// Returns the ID of the PubSub item.
diff --git a/src/base/QXmppPubSubItem.h b/src/base/QXmppPubSubItem.h
index e585de9d4..a8058a6cd 100644
--- a/src/base/QXmppPubSubItem.h
+++ b/src/base/QXmppPubSubItem.h
@@ -20,11 +20,11 @@ class QXMPP_EXPORT QXmppPubSubItem
public:
QXmppPubSubItem(const QString &id = {}, const QString &publisher = {});
QXmppPubSubItem(const QXmppPubSubItem &);
- QXmppPubSubItem(QXmppPubSubItem &&);
+ QXmppPubSubItem(QXmppPubSubItem &&) noexcept;
virtual ~QXmppPubSubItem();
QXmppPubSubItem &operator=(const QXmppPubSubItem &);
- QXmppPubSubItem &operator=(QXmppPubSubItem &&);
+ QXmppPubSubItem &operator=(QXmppPubSubItem &&) noexcept;
QString id() const;
void setId(const QString &id);
diff --git a/src/base/QXmppStickerPackItem.cpp b/src/base/QXmppStickerPackItem.cpp
new file mode 100644
index 000000000..215dbaa1b
--- /dev/null
+++ b/src/base/QXmppStickerPackItem.cpp
@@ -0,0 +1,278 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppStickerPackItem.h"
+
+#include "QXmppConstants_p.h"
+#include "QXmppEncryptedFileSource.h"
+#include "QXmppFileMetadata.h"
+
+#include
+
+class QXmppStickerItemPrivate : public QSharedData
+{
+public:
+ QXmppFileMetadata metadata;
+ QVector httpSources;
+ QVector encryptedSources;
+ QVector suggest;
+};
+
+QXmppStickerItem::QXmppStickerItem()
+ : d(new QXmppStickerItemPrivate())
+{
+}
+
+QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppStickerItem)
+
+///
+/// \class QXmppStickerItem
+///
+/// This class represents a single sticker when publishing or retrieving it.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \brief Returns metadata about the sticker file
+///
+const QXmppFileMetadata &QXmppStickerItem::metadata() const
+{
+ return d->metadata;
+}
+
+///
+/// \brief Sets metadata of this sticker file
+///
+void QXmppStickerItem::setMetadata(const QXmppFileMetadata &metadata)
+{
+ d->metadata = metadata;
+}
+
+///
+/// \brief Returns HTTP sources for the sticker file
+///
+const QVector &QXmppStickerItem::httpSource() const
+{
+ return d->httpSources;
+}
+
+///
+/// \brief Sets the list of HTTP sources for this sticker file
+///
+void QXmppStickerItem::setHttpSources(const QVector &httpSources)
+{
+ d->httpSources = httpSources;
+}
+
+///
+/// \brief Returns the list of encrypted sources for this sticker file
+///
+const QVector &QXmppStickerItem::encryptedSources() const
+{
+ return d->encryptedSources;
+}
+
+///
+/// \brief Set the list of encrypted sources for this sticker file
+///
+void QXmppStickerItem::setEncryptedSources(const QVector &encryptedSources)
+{
+ d->encryptedSources = encryptedSources;
+}
+
+///
+/// \brief Returns words for which apps can suggest the use of this sticker
+///
+const QVector &QXmppStickerItem::suggestedWords() const
+{
+ return d->suggest;
+}
+
+///
+/// \brief Sets the words for which apps can suggest the use of this sticker
+///
+void QXmppStickerItem::setSuggestedWords(const QVector &suggest)
+{
+ d->suggest = suggest;
+}
+
+/// \cond
+void QXmppStickerItem::toXml(QXmlStreamWriter *writer) const
+{
+ writer->writeStartElement("item");
+ d->metadata.toXml(writer);
+ writer->writeStartElement("sources");
+ writer->writeDefaultNamespace(ns_sfs);
+ for (const auto &httpSource : d->httpSources) {
+ httpSource.toXml(writer);
+ }
+ for (const auto &encryptedSource : d->encryptedSources) {
+ encryptedSource.toXml(writer);
+ }
+ writer->writeEndElement();
+ for (const auto &word : d->suggest) {
+ writer->writeTextElement("suggest", word);
+ }
+ writer->writeEndElement();
+}
+
+bool QXmppStickerItem::parse(const QDomElement &element)
+{
+ auto fileElement = element.firstChildElement("file");
+ d->metadata.parse(fileElement);
+
+ auto sources = element.firstChildElement("sources");
+ for (auto sourceEl = sources.firstChildElement();
+ !sourceEl.isNull();
+ sourceEl = sourceEl.nextSiblingElement()) {
+ if (sourceEl.tagName() == QStringLiteral("url-data")) {
+ QXmppHttpFileSource source;
+ if (source.parse(sourceEl)) {
+ d->httpSources.push_back(std::move(source));
+ }
+ } else if (sourceEl.tagName() == QStringLiteral("encrypted")) {
+ QXmppEncryptedFileSource source;
+ if (source.parse(sourceEl)) {
+ d->encryptedSources.push_back(std::move(source));
+ }
+ }
+ }
+
+ for (auto suggestEl = element.firstChildElement("suggest");
+ !suggestEl.isNull();
+ suggestEl = suggestEl.nextSiblingElement("suggest")) {
+ d->suggest.push_back(suggestEl.text());
+ }
+
+ return true;
+}
+/// \endcond
+
+class QXmppStickerPackItemPrivate : public QSharedData
+{
+public:
+ QString name;
+ QString summary;
+ QVector items;
+ bool restricted = false;
+ QXmppHash hash;
+};
+
+QXmppStickerPackItem::QXmppStickerPackItem()
+ : d(new QXmppStickerPackItemPrivate())
+{
+}
+
+QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppStickerPackItem)
+
+///
+/// \class QXmppStickerPackitem
+///
+/// A pubsub item that represents a sticker pack.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \brief Returns the name of the sticker pack
+///
+const QString &QXmppStickerPackItem::name() const
+{
+ return d->name;
+}
+
+///
+/// \brief Sets the name of the sticker pack
+///
+void QXmppStickerPackItem::setName(const QString &name)
+{
+ d->name = name;
+}
+
+///
+/// \brief Returns the summary of this sticker pack
+///
+const QString &QXmppStickerPackItem::summary() const
+{
+ return d->summary;
+}
+
+///
+/// \brief Sets the summary of the sticker pack
+///
+void QXmppStickerPackItem::setSummary(const QString &summary)
+{
+ d->summary = summary;
+}
+
+///
+/// \brief Returns the list of stickers of this pack
+///
+const QVector &QXmppStickerPackItem::items() const
+{
+ return d->items;
+}
+
+///
+/// \brief Set the list of stickers for this pack
+///
+void QXmppStickerPackItem::setItems(const QVector &items)
+{
+ d->items = items;
+}
+
+///
+/// \brief Returns whether this sticker pack can be freely imported
+///
+bool QXmppStickerPackItem::restricted() const
+{
+ return d->restricted;
+}
+
+///
+/// \brief Set whether this sticker pack should be importable by others
+///
+void QXmppStickerPackItem::setRestricted(bool restricted)
+{
+ d->restricted = restricted;
+}
+
+void QXmppStickerPackItem::parsePayload(const QDomElement &payloadElement)
+{
+ d->name = payloadElement.firstChildElement("name").text();
+ d->summary = payloadElement.firstChildElement("summary").text();
+
+ for (auto firstChild = payloadElement.firstChildElement("item");
+ !firstChild.isNull();
+ firstChild = firstChild.nextSiblingElement("item")) {
+ QXmppStickerItem stickerItem;
+ stickerItem.parse(firstChild);
+
+ d->items.push_back(std::move(stickerItem));
+ }
+
+ d->hash.parse(payloadElement.firstChildElement("hash"));
+ d->restricted = !payloadElement.firstChildElement("restricted").isNull();
+}
+
+void QXmppStickerPackItem::serializePayload(QXmlStreamWriter *writer) const
+{
+ writer->writeStartElement("pack");
+ writer->writeDefaultNamespace(ns_stickers);
+
+ writer->writeTextElement("name", d->name);
+ writer->writeTextElement("summary", d->summary);
+
+ for (const auto &item : d->items) {
+ item.toXml(writer);
+ }
+
+ d->hash.toXml(writer);
+
+ if (d->restricted) {
+ writer->writeEmptyElement("restricted");
+ }
+ writer->writeEndElement();
+}
diff --git a/src/base/QXmppStickerPackItem.h b/src/base/QXmppStickerPackItem.h
new file mode 100644
index 000000000..4fd18b0ec
--- /dev/null
+++ b/src/base/QXmppStickerPackItem.h
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: 2022 Jonah Brüchert
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPSTICKERPACKITEM_H
+#define QXMPPSTICKERPACKITEM_H
+
+#include "QXmppPubSubItem.h"
+
+#include
+
+#include
+
+class QXmppFileMetadata;
+class QXmppHttpFileSource;
+class QXmppEncryptedFileSource;
+
+class QXmppStickerItemPrivate;
+
+class QXMPP_EXPORT QXmppStickerItem
+{
+public:
+ QXmppStickerItem();
+
+ QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppStickerItem)
+
+ const QXmppFileMetadata &metadata() const;
+ void setMetadata(const QXmppFileMetadata &metadata);
+
+ const QVector &httpSource() const;
+ void setHttpSources(const QVector &httpSources);
+
+ const QVector &encryptedSources() const;
+ void setEncryptedSources(const QVector &encryptedSources);
+
+ const QVector &suggestedWords() const;
+ void setSuggestedWords(const QVector &suggest);
+
+ /// \cond
+ bool parse(const QDomElement &element);
+ void toXml(QXmlStreamWriter *writer) const;
+ /// \endcond
+
+private:
+ QSharedDataPointer d;
+};
+
+class QXmppStickerPackItemPrivate;
+
+class QXMPP_EXPORT QXmppStickerPackItem : public QXmppPubSubItem
+{
+public:
+ QXmppStickerPackItem();
+
+ QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppStickerPackItem)
+
+ const QString &name() const;
+ void setName(const QString &name);
+
+ const QString &summary() const;
+ void setSummary(const QString &summary);
+
+ const QVector &items() const;
+ void setItems(const QVector &items);
+
+ bool restricted() const;
+ void setRestricted(bool restricted);
+
+ void parsePayload(const QDomElement &payloadElement) override;
+ void serializePayload(QXmlStreamWriter *writer) const override;
+
+private:
+ QSharedDataPointer d;
+};
+
+#endif // QXMPPSTICKERPACKITEM_H
diff --git a/tests/qxmppmessage/tst_qxmppmessage.cpp b/tests/qxmppmessage/tst_qxmppmessage.cpp
index 1aa2c6735..3c0848953 100644
--- a/tests/qxmppmessage/tst_qxmppmessage.cpp
+++ b/tests/qxmppmessage/tst_qxmppmessage.cpp
@@ -56,6 +56,7 @@ private slots:
void testE2eeFallbackBody();
void testFileSharing();
void testEncryptedFileSource();
+ void testStickers();
};
void tst_QXmppMessage::testBasic_data()
@@ -1259,5 +1260,32 @@ void tst_QXmppMessage::testEncryptedFileSource()
}
}
+void tst_QXmppMessage::testStickers()
+{
+ QByteArray xml(
+ ""
+ "😘"
+ ""
+ ""
+ "😘"
+ "gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY="
+ "image/png"
+ "67016"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "");
+
+ QXmppMessage message1;
+ parsePacket(message1, xml);
+ QVERIFY(!message1.sharedFiles().empty());
+ QVERIFY(message1.stickerPackId().has_value());
+ QCOMPARE(message1.stickerPackId().value(), QStringLiteral("EpRv28DHHzFrE4zd+xaNpVb4"));
+ serializePacket(message1, xml);
+}
+
QTEST_MAIN(tst_QXmppMessage)
#include "tst_qxmppmessage.moc"
diff --git a/tests/qxmpppubsub/tst_qxmpppubsub.cpp b/tests/qxmpppubsub/tst_qxmpppubsub.cpp
index b34f34cfd..fc5d931f0 100644
--- a/tests/qxmpppubsub/tst_qxmpppubsub.cpp
+++ b/tests/qxmpppubsub/tst_qxmpppubsub.cpp
@@ -2,8 +2,10 @@
//
// SPDX-License-Identifier: LGPL-2.1-or-later
+#include "QXmppHttpFileSource.h"
#include "QXmppPubSubAffiliation.h"
#include "QXmppPubSubSubscription.h"
+#include "QXmppStickerPackItem.h"
#include "pubsubutil.h"
#include "util.h"
@@ -55,6 +57,7 @@ class tst_QXmppPubSub : public QObject
Q_SLOT void testIsItem_data();
Q_SLOT void testIsItem();
Q_SLOT void testTestItem();
+ Q_SLOT void testStickerPackItem();
};
void tst_QXmppPubSub::testAffiliation_data()
@@ -280,5 +283,57 @@ void tst_QXmppPubSub::testTestItem()
QVERIFY(!TestItem::isItem(xmlToDom(invalidXml)));
}
+void tst_QXmppPubSub::testStickerPackItem()
+{
+ QByteArray xml(
+ "- "
+ ""
+ "Marsey the Cat"
+ "Be cute or be cynical, this little kitten works both ways."
+ "
- "
+ ""
+ "👍"
+ "0AdP8lJOWJrugSKOIAqfEKqFatIpG5JBCjjxY253ojQ="
+ "512"
+ "image/png"
+ "71045"
+ "512"
+ ""
+ ""
+ ""
+ ""
+ "+1"
+ "thumbsup"
+ "
"
+ "- "
+ ""
+ "😘"
+ "gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY="
+ "512"
+ "image/png"
+ "67016"
+ "512"
+ ""
+ ""
+ ""
+ ""
+ "
"
+ "EpRv28DHHzFrE4zd+xaNpVb4jbu4s74XtioExNjQzZ0="
+ ""
+ " ");
+
+ QXmppStickerPackItem item;
+ parsePacket(item, xml);
+ QCOMPARE(item.items().size(), 2);
+ QCOMPARE(item.name(), QStringLiteral("Marsey the Cat"));
+ QCOMPARE(item.summary(), QStringLiteral("Be cute or be cynical, this little kitten works both ways."));
+ QVERIFY(!item.restricted());
+
+ auto &firstItem = item.items().front();
+ QCOMPARE(firstItem.suggestedWords().size(), 2);
+ QCOMPARE(firstItem.httpSource().front().url(), QUrl("https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_thumbs_up.png"));
+ serializePacket(item, xml);
+}
+
QTEST_MAIN(tst_QXmppPubSub)
#include "tst_qxmpppubsub.moc"