mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 03:52:06 +01:00
Add support for XEP-0382: Spoiler Messages
Fixes SMACK-795.
This commit is contained in:
parent
a729a7c43b
commit
ce19ea4114
11 changed files with 530 additions and 0 deletions
|
@ -94,6 +94,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
|
|||
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
|
||||
| Stable and Unique Stanza IDs | [XEP-0359](http://xmpp.org/extensions/xep-0359.html) | This specification describes unique and stable IDs for messages. |
|
||||
| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
|
||||
| [Spoiler Messages](spoiler.md) | [XEP-0382](http://xmpp.org/extensions/xep-0382.html) | Indicate that the body of a message should be treated as a spoiler |
|
||||
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
|
||||
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
|
||||
| [Consistent Color Generation](consistent_colors.md) | [XEP-0392](http://xmpp.org/extensions/xep-0392.html) | Generate consistent colors for identifiers like usernames to provide a consistent user experience. |
|
||||
|
|
33
documentation/extensions/spoiler.md
Normal file
33
documentation/extensions/spoiler.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
Spoiler Messages
|
||||
================
|
||||
|
||||
[Back](index.md)
|
||||
|
||||
Spoiler Messages can be used to indicate that the body of a message is a spoiler and should be displayed as such.
|
||||
|
||||
## Usage
|
||||
|
||||
To get an instance of the SpoilerManager, call
|
||||
```
|
||||
SpoilerManager manager = SpoilerManager.getInstanceFor(connection);
|
||||
```
|
||||
This will automatically add Spoilers to the list of supported features of your client.
|
||||
|
||||
The manager can then be used to add SpoilerElements to messages like follows:
|
||||
```
|
||||
Message message = new Message();
|
||||
|
||||
// spoiler without hint
|
||||
SpoilerElement.addSpoiler(message);
|
||||
|
||||
// spoiler with hint about content
|
||||
SpoilerElement.addSpoiler(message, "End of Love Story");
|
||||
|
||||
// spoiler with localized hint
|
||||
SpoilerElement.addSpoiler(message, "de", "Der Kuchen ist eine Lüge");
|
||||
```
|
||||
|
||||
To get Spoilers from a message call
|
||||
```
|
||||
Map<String, String> spoilers = SpoilerElement.getSpoilers(message);
|
||||
```
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
|
||||
public final class SpoilerManager extends Manager {
|
||||
|
||||
public static final String NAMESPACE_0 = "urn:xmpp:spoiler:0";
|
||||
|
||||
private static final Map<XMPPConnection, SpoilerManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new SpoilerManager and add Spoiler to disco features.
|
||||
*
|
||||
* @param connection xmpp connection
|
||||
*/
|
||||
private SpoilerManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin announcing support for Spoiler messages.
|
||||
*/
|
||||
public void startAnnounceSupport() {
|
||||
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* End announcing support for Spoiler messages.
|
||||
*/
|
||||
public void stopAnnounceSupport() {
|
||||
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the connections instance of the SpoilerManager.
|
||||
*
|
||||
* @param connection xmpp connection
|
||||
* @return SpoilerManager
|
||||
*/
|
||||
public static SpoilerManager getInstanceFor(XMPPConnection connection) {
|
||||
SpoilerManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new SpoilerManager(connection);
|
||||
INSTANCES.put(connection, manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler.element;
|
||||
|
||||
import static org.jivesoftware.smackx.spoiler.SpoilerManager.NAMESPACE_0;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class SpoilerElement implements ExtensionElement {
|
||||
|
||||
public static final String ELEMENT = "spoiler";
|
||||
public static final SpoilerElement EMPTY = new SpoilerElement(null, null);
|
||||
|
||||
private final String hint;
|
||||
private final String language;
|
||||
|
||||
/**
|
||||
* Create a new SpoilerElement with a hint about a content and a language attribute.
|
||||
*
|
||||
* @param language language of the hint.
|
||||
* @param hint hint about the content.
|
||||
*/
|
||||
public SpoilerElement(String language, String hint) {
|
||||
if (language != null && !language.equals("")) {
|
||||
if (hint == null || hint.equals("")) {
|
||||
throw new IllegalArgumentException("Hint cannot be null or empty if language is not empty.");
|
||||
}
|
||||
}
|
||||
this.language = language;
|
||||
this.hint = hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hint text of the spoiler.
|
||||
* May be null.
|
||||
*
|
||||
* @return hint text
|
||||
*/
|
||||
public String getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a SpoilerElement to a message.
|
||||
*
|
||||
* @param message message to add the Spoiler to.
|
||||
*/
|
||||
public static void addSpoiler(Message message) {
|
||||
message.addExtension(SpoilerElement.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a SpoilerElement with a hint to a message.
|
||||
*
|
||||
* @param message Message to add the Spoiler to.
|
||||
* @param hint Hint about the Spoilers content.
|
||||
*/
|
||||
public static void addSpoiler(Message message, String hint) {
|
||||
message.addExtension(new SpoilerElement(null, hint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a SpoilerElement with a hint in a certain language to a message.
|
||||
*
|
||||
* @param message Message to add the Spoiler to.
|
||||
* @param lang language of the Spoiler hint.
|
||||
* @param hint hint.
|
||||
*/
|
||||
public static void addSpoiler(Message message, String lang, String hint) {
|
||||
message.addExtension(new SpoilerElement(lang, hint));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true, if the message has at least one spoiler element.
|
||||
*
|
||||
* @param message message
|
||||
* @return true if message has spoiler extension
|
||||
*/
|
||||
public static boolean containsSpoiler(Message message) {
|
||||
return message.hasExtension(SpoilerElement.ELEMENT, NAMESPACE_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of all spoilers contained in a message.
|
||||
* The map uses the language of a spoiler as key.
|
||||
* If a spoiler has no language attribute, its key will be an empty String.
|
||||
*
|
||||
* @param message message
|
||||
* @return map of spoilers
|
||||
*/
|
||||
public static Map<String, String> getSpoilers(Message message) {
|
||||
if (!containsSpoiler(message)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ExtensionElement> spoilers = message.getExtensions(SpoilerElement.ELEMENT, NAMESPACE_0);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
|
||||
for (ExtensionElement e : spoilers) {
|
||||
SpoilerElement s = (SpoilerElement) e;
|
||||
if (s.getLanguage() == null || s.getLanguage().equals("")) {
|
||||
map.put("", s.getHint());
|
||||
} else {
|
||||
map.put(s.getLanguage(), s.getHint());
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the language of the hint.
|
||||
* May be null.
|
||||
*
|
||||
* @return language of hint text
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE_0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.optXmlLangAttribute(getLanguage());
|
||||
if (getHint() == null) {
|
||||
xml.closeEmptyElement();
|
||||
} else {
|
||||
xml.rightAngleBracket();
|
||||
xml.append(getHint());
|
||||
xml.closeElement(this);
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Smack's API for XEP-0382: Spoiler Messages.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler.element;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Smack's API for XEP-0382: Spoiler Messages.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler;
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler.provider;
|
||||
|
||||
import static org.xmlpull.v1.XmlPullParser.END_TAG;
|
||||
import static org.xmlpull.v1.XmlPullParser.TEXT;
|
||||
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
import org.jivesoftware.smackx.spoiler.element.SpoilerElement;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
public class SpoilerProvider extends ExtensionElementProvider<SpoilerElement> {
|
||||
|
||||
public static SpoilerProvider INSTANCE = new SpoilerProvider();
|
||||
|
||||
@Override
|
||||
public SpoilerElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
String lang = ParserUtils.getXmlLang(parser);
|
||||
String hint = null;
|
||||
|
||||
outerloop: while (true) {
|
||||
int tag = parser.next();
|
||||
switch (tag) {
|
||||
case TEXT:
|
||||
hint = parser.getText();
|
||||
break;
|
||||
case END_TAG:
|
||||
break outerloop;
|
||||
}
|
||||
}
|
||||
return new SpoilerElement(lang, hint);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Smack's API for XEP-0382: Spoiler Messages.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler.provider;
|
|
@ -296,6 +296,13 @@
|
|||
<className>org.jivesoftware.smackx.eme.provider.ExplicitMessageEncryptionProvider</className>
|
||||
</extensionProvider>
|
||||
|
||||
<!-- XEP-0382: Spoiler Messages -->
|
||||
<extensionProvider>
|
||||
<elementName>spoiler</elementName>
|
||||
<namespace>urn:xmpp:spoiler:0</namespace>
|
||||
<className>org.jivesoftware.smackx.spoiler.provider.SpoilerProvider</className>
|
||||
</extensionProvider>
|
||||
|
||||
<!-- XEP-0394: Message Markup -->
|
||||
<extensionProvider>
|
||||
<elementName>markup</elementName>
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||
import org.jivesoftware.smack.test.util.TestUtils;
|
||||
import org.jivesoftware.smackx.spoiler.element.SpoilerElement;
|
||||
import org.jivesoftware.smackx.spoiler.provider.SpoilerProvider;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
public class SpoilerTest extends SmackTestSuite {
|
||||
|
||||
@Test
|
||||
public void emptySpoilerTest() throws Exception {
|
||||
final String xml = "<spoiler xmlns='urn:xmpp:spoiler:0'/>";
|
||||
|
||||
Message message = new Message();
|
||||
SpoilerElement.addSpoiler(message);
|
||||
|
||||
SpoilerElement empty = message.getExtension(SpoilerElement.ELEMENT, SpoilerManager.NAMESPACE_0);
|
||||
|
||||
assertNull(empty.getHint());
|
||||
assertNull(empty.getLanguage());
|
||||
|
||||
assertXMLEqual(xml, empty.toXML().toString());
|
||||
|
||||
XmlPullParser parser = TestUtils.getParser(xml);
|
||||
SpoilerElement parsed = SpoilerProvider.INSTANCE.parse(parser);
|
||||
assertXMLEqual(xml, parsed.toXML().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hintSpoilerTest() throws Exception {
|
||||
final String xml = "<spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>";
|
||||
|
||||
Message message = new Message();
|
||||
SpoilerElement.addSpoiler(message, "Love story end");
|
||||
|
||||
SpoilerElement withHint = message.getExtension(SpoilerElement.ELEMENT, SpoilerManager.NAMESPACE_0);
|
||||
|
||||
assertEquals("Love story end", withHint.getHint());
|
||||
assertNull(withHint.getLanguage());
|
||||
|
||||
assertXMLEqual(xml, withHint.toXML().toString());
|
||||
|
||||
XmlPullParser parser = TestUtils.getParser(xml);
|
||||
SpoilerElement parsed = SpoilerProvider.INSTANCE.parse(parser);
|
||||
|
||||
assertXMLEqual(xml, parsed.toXML().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void i18nHintSpoilerTest() throws Exception {
|
||||
final String xml = "<spoiler xml:lang='de' xmlns='urn:xmpp:spoiler:0'>Der Kuchen ist eine Lüge!</spoiler>";
|
||||
|
||||
Message message = new Message();
|
||||
SpoilerElement.addSpoiler(message, "de", "Der Kuchen ist eine Lüge!");
|
||||
|
||||
SpoilerElement i18nHint = message.getExtension(SpoilerElement.ELEMENT, SpoilerManager.NAMESPACE_0);
|
||||
|
||||
assertEquals("Der Kuchen ist eine Lüge!", i18nHint.getHint());
|
||||
assertEquals("de", i18nHint.getLanguage());
|
||||
|
||||
assertXMLEqual(xml, i18nHint.toXML().toString());
|
||||
|
||||
XmlPullParser parser = TestUtils.getParser(xml);
|
||||
SpoilerElement parsed = SpoilerProvider.INSTANCE.parse(parser);
|
||||
assertEquals(i18nHint.getLanguage(), parsed.getLanguage());
|
||||
|
||||
assertXMLEqual(xml, parsed.toXML().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSpoilersTest() {
|
||||
Message m = new Message();
|
||||
assertNull(SpoilerElement.getSpoilers(m));
|
||||
|
||||
SpoilerElement.addSpoiler(m);
|
||||
assertTrue(SpoilerElement.containsSpoiler(m));
|
||||
|
||||
Map<String, String> spoilers = SpoilerElement.getSpoilers(m);
|
||||
assertEquals(1, spoilers.size());
|
||||
assertEquals(null, spoilers.get(""));
|
||||
|
||||
final String spoilerText = "Spoiler Text";
|
||||
|
||||
SpoilerElement.addSpoiler(m, "de", spoilerText);
|
||||
spoilers = SpoilerElement.getSpoilers(m);
|
||||
assertEquals(2, spoilers.size());
|
||||
assertEquals(spoilerText, spoilers.get("de"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void spoilerCheckArgumentsNullTest() {
|
||||
SpoilerElement spoilerElement = new SpoilerElement("de", null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void spoilerCheckArgumentsEmptyTest() {
|
||||
SpoilerElement spoilerElement = new SpoilerElement("de", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Smack's API for XEP-0382: Spoiler Messages.
|
||||
*/
|
||||
package org.jivesoftware.smackx.spoiler;
|
Loading…
Reference in a new issue