diff --git a/src/main/java/fr/delthas/skype/Chat.java b/src/main/java/fr/delthas/skype/Chat.java
new file mode 100644
index 0000000..1123929
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/Chat.java
@@ -0,0 +1,61 @@
+package fr.delthas.skype;
+
+import fr.delthas.skype.message.Message;
+
+/**
+ * Interface for chats
+ */
+public interface Chat {
+
+ /**
+ * Get Skype client
+ *
+ * @return skype object
+ */
+ Skype getSkype();
+
+ /**
+ * Get identity of a chat
+ *
+ * @return string
+ */
+ String getIdentity();
+
+ /**
+ * Type of chat
+ *
+ * @return ChatType enum item
+ */
+ ChatType getType();
+
+ /**
+ * Send message to chat
+ *
+ * @param message object of one of messages types
+ */
+ void sendMessage(Message message);
+
+ /**
+ * Edit message to chat.
+ *
+ * Work only with text message.
+ *
+ * @param message object of one of messages types
+ */
+ void editMessage(Message message);
+
+ /**
+ * Remove message to chat
+ *
+ * @param message object of one of messages types
+ */
+ void removeMessage(Message message);
+
+ /**
+ * ChatType enum
+ */
+ public enum ChatType {
+ GROUP,
+ USER
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/Constants.java b/src/main/java/fr/delthas/skype/Constants.java
new file mode 100644
index 0000000..43c9b12
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/Constants.java
@@ -0,0 +1,14 @@
+package fr.delthas.skype;
+
+/**
+ * Constants
+ */
+public class Constants {
+ public static String HEADER_TEXT = "Text";
+ public static String HEADER_RICH_TEXT = "RichText";
+ public static String HEADER_PICTURE = "RichText/UriObject";
+ public static String HEADER_FILE = "RichText/Media_GenericFile";
+ public static String HEADER_VIDEO = "RichText/Media_Video";
+ public static String HEADER_CONTACT = "RichText/Contacts";
+ public static String HEADER_MOJI = "RichText/Media_FlikMsg";
+}
diff --git a/src/main/java/fr/delthas/skype/Group.java b/src/main/java/fr/delthas/skype/Group.java
index b56b473..217683c 100644
--- a/src/main/java/fr/delthas/skype/Group.java
+++ b/src/main/java/fr/delthas/skype/Group.java
@@ -1,5 +1,7 @@
package fr.delthas.skype;
+import fr.delthas.skype.message.Message;
+
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -10,9 +12,8 @@
* A conversation between some Skype users.
*
* All information will be updated as updates are received (this object is NOT an immutable view/snapshot of a group).
- *
*/
-public class Group {
+public class Group implements Chat {
private final Skype skype;
private final String id;
@@ -31,15 +32,28 @@ public class Group {
*
* @param message The message to send.
*/
+ @Deprecated
public void sendMessage(String message) {
skype.sendGroupMessage(this, message);
}
+ public void sendMessage(Message message) {
+ skype.doMessageAction(this, message, Skype.MessageAction.SEND);
+ }
+
+ public void editMessage(Message message) {
+ skype.doMessageAction(this, message, Skype.MessageAction.EDIT);
+ }
+
+ public void removeMessage(Message message) {
+ skype.doMessageAction(this, message, Skype.MessageAction.REMOVE);
+ }
+
/**
* The id of a group is a special String used by Skype to uniquely identify groups.
*
- * In case you know about network IDs and the like: if a group "adress" is "19:xxx@thread.skype", its id is "xxx".
- *
+ * In case you know about network IDs and the like: if a group "address" is "19:xxx@thread.skype", its id is "19:xxx@thread.skype" or "19:xxx@p2p.thread.skype" .
+ *
* @return The id of the group.
*/
public String getId() {
@@ -89,7 +103,6 @@ void setUsers(List> users) {
*
* @param user The user to add to this group.
* @param role The role of the newly added user.
- *
* @return true if the user wasn't in the group, and the Skype account has group admin rights if needed.
*/
public boolean addUser(User user, Role role) {
@@ -111,9 +124,7 @@ public boolean addUser(User user, Role role) {
* Removes a user from this group. Group admin rights are needed.
*
* @param user The user to remove from this group.
- *
* @return true if the Skype account has admin rights, and the user was in the group.
- *
* @see #isSelfAdmin()
*/
public boolean removeUser(User user) {
@@ -138,9 +149,7 @@ public boolean removeUser(User user) {
*
* @param user The user whose role is to be changed
* @param role The new role of the user.
- *
* @return true if the Skype account has admin rights, and the user was in the group and didn't have this role already.
- *
* @see #isSelfAdmin()
*/
public boolean changeUserRole(User user, Role role) {
@@ -213,4 +222,18 @@ public String toString() {
return "Group: " + getId();
}
+ @Override
+ public Skype getSkype() {
+ return skype;
+ }
+
+ @Override
+ public String getIdentity() {
+ return id;
+ }
+
+ @Override
+ public ChatType getType() {
+ return ChatType.GROUP;
+ }
}
diff --git a/src/main/java/fr/delthas/skype/GroupMessageListener.java b/src/main/java/fr/delthas/skype/GroupMessageListener.java
index 64e0426..25d2242 100644
--- a/src/main/java/fr/delthas/skype/GroupMessageListener.java
+++ b/src/main/java/fr/delthas/skype/GroupMessageListener.java
@@ -1,19 +1,52 @@
package fr.delthas.skype;
+import fr.delthas.skype.message.Message;
/**
* A listener for new messages sent to a Skype account.
- *
*/
-public interface GroupMessageListener {
+public interface GroupMessageListener extends MessageListener {
/**
* Called when a message is sent from a user to a group the Skype account is in while it is connected.
*
- * @param group The group in which the message has been sent.
- * @param sender The sender of the message.
- * @param message The message sent.
+ * @param group The group in which the message has been sent.
+ * @param sender The sender of the message.
+ * @param message The text of message sent.
*/
- public void messageReceived(Group group, User sender, String message);
+ @Deprecated
+ default void messageReceived(Group group, User sender, String message) {
+ }
+
+ /**
+ * Called when a message is sent from a user to a group the Skype account is in while it is connected.
+ *
+ * @param group The group in which the message has been sent.
+ * @param sender The sender of the message.
+ * @param message The object of message sent.
+ */
+ default void messageReceived(Group group, User sender, Message message) {
+ }
+
+ /**
+ * Called when a message is edited by a user in a group the Skype account is in while it is connected.
+ *
+ * @param group The group in which the message has been edit.
+ * @param sender The sender of the message.
+ * @param message The object of message sent.
+ */
+ default void messageEdited(Group group, User sender, Message message) {
+ }
+
+ /**
+ * Called when a message is removed by a user in a group the Skype account is in while it is connected.
+ *
+ * @param group The group in which the message has been remove.
+ * @param sender The sender of the message.
+ * @param message The object of message sent.
+ */
+ default void messageRemoved(Group group, User sender, Message message) {
+ }
+
}
diff --git a/src/main/java/fr/delthas/skype/MessageListener.java b/src/main/java/fr/delthas/skype/MessageListener.java
new file mode 100644
index 0000000..bd52c80
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/MessageListener.java
@@ -0,0 +1,12 @@
+package fr.delthas.skype;
+
+/**
+ * A root listener for new messages sent to a Skype account.
+ */
+public interface MessageListener {
+ enum MessageEvent {
+ RECEIVED,
+ EDITED,
+ REMOVED
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/NotifConnector.java b/src/main/java/fr/delthas/skype/NotifConnector.java
index 2ceb61d..213bb6f 100644
--- a/src/main/java/fr/delthas/skype/NotifConnector.java
+++ b/src/main/java/fr/delthas/skype/NotifConnector.java
@@ -1,14 +1,24 @@
package fr.delthas.skype;
-import java.io.BufferedInputStream;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.StringReader;
+import fr.delthas.skype.MessageListener.MessageEvent;
+import fr.delthas.skype.message.*;
+import org.jsoup.Jsoup;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.net.ssl.SSLSocketFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@@ -18,17 +28,8 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import javax.net.ssl.SSLSocketFactory;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.jsoup.Jsoup;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
+import static fr.delthas.skype.Constants.HEADER_RICH_TEXT;
+import static fr.delthas.skype.Constants.HEADER_TEXT;
class NotifConnector {
@@ -102,7 +103,8 @@ private void processPacket(Packet packet) throws IOException {
case "recentconversations-response":
NodeList conversationNodes = doc.getElementsByTagName("conversation");
List threadIds = new ArrayList<>();
- outer: for (int i = 0; i < conversationNodes.getLength(); i++) {
+ outer:
+ for (int i = 0; i < conversationNodes.getLength(); i++) {
Node conversation = conversationNodes.item(i);
String id = null;
boolean isThread = false;
@@ -146,7 +148,7 @@ private void processPacket(Packet packet) throws IOException {
sb.delete(0, sb.length());
sb.append("");
}
- sb.append("19:").append(threadId).append("@thread.skype");
+ sb.append("").append(threadId).append("");
}
String body = sb.append("").toString();
sendPacket("GET", "MSGR\\THREADS", body);
@@ -183,56 +185,110 @@ private void processPacket(Packet packet) throws IOException {
}
Object sender = parseEntity(formatted.sender);
Object receiver = parseEntity(formatted.receiver);
- switch (messageType) {
- case "Text":
- case "RichText":
- if (!(sender instanceof User)) {
- logger.fine("Received " + messageType + " message sent from " + sender + " which isn't a user");
- break;
- }
- if (receiver instanceof Group) {
- skype.groupMessageReceived((Group) receiver, (User) sender, getPlaintext(formatted.body));
- } else {
- skype.userMessageReceived((User) sender, getPlaintext(formatted.body));
- }
- break;
- case "ThreadActivity/AddMember":
- List usernames = getXMLFields(formatted.body, "target");
- skype.usersAddedToGroup(usernames.stream().map(username -> (User) parseEntity(username)).collect(Collectors.toList()), (Group) sender);
+ if (messageType.startsWith(HEADER_RICH_TEXT) || messageType.startsWith(HEADER_TEXT)) {
+ // work with messages
+ if (!(sender instanceof User)) {
+ logger.fine("Received " + messageType + " message sent from " + sender + " which isn't a user");
break;
- case "ThreadActivity/DeleteMember":
- usernames = getXMLFields(formatted.body, "target");
- skype.usersAddedToGroup(usernames.stream().map(username -> (User) parseEntity(username)).collect(Collectors.toList()), (Group) sender);
- break;
- case "ThreadActivity/TopicUpdate":
- skype.groupTopicChanged((Group) sender, getPlaintext(getXMLField(formatted.body, "value")));
+ }
+ boolean isRemoved = false;
+
+ String isMe = formatted.headers.get("Skype-EmoteOffset");
+ String messageId = formatted.headers.get("Client-Message-ID");
+ String editId = formatted.headers.get("Skype-EditedId");
+ String html = formatted.body;
+ if (isMe != null) {
+ html = formatted.body.substring(Integer.parseInt(isMe));
+ }
+ if (editId != null) {
+ String editOffset = formatted.headers.get("Skype-EditOffset");
+ if (editOffset != null) {
+ Integer offset = Integer.parseInt(editOffset);
+ html = html.substring(offset);
+ }
+ html = html.replaceAll("", "");
+ isRemoved = html.isEmpty();
+ }
+ String id = messageId != null ? messageId : editId;
+ Message message = null;
+ MessageType type = MessageType.getTypeByHeaderType((messageType));
+ if (type == null) {
break;
- case "ThreadActivity/RoleUpdate":
- Document doc = getDocument(formatted.body);
- NodeList targetNodes = doc.getElementsByTagName("target");
- List> roles = new ArrayList<>(targetNodes.getLength());
- for (int i = 0; i < targetNodes.getLength(); i++) {
- Node targetNode = targetNodes.item(i);
- User user = null;
- Role role = null;
- for (int j = 0; j < targetNode.getChildNodes().getLength(); j++) {
- Node targetPropertyNode = targetNode.getChildNodes().item(j);
- if (targetPropertyNode.getNodeName().equals("id")) {
- user = (User) parseEntity(targetPropertyNode.getTextContent());
- skype.updateUser(user);
- } else if (targetPropertyNode.getNodeName().equals("role")) {
- role = Role.getRole(targetPropertyNode.getTextContent());
+ }
+ switch (type) {
+ case TEXT:
+ String singleRow = html.replaceAll("\r", "").replaceAll("\n", "");
+ Boolean isHasQuotes = Pattern.compile(".*.*<\\/quote>.*").matcher(singleRow).matches();
+ message = new TextMessage(id, html, isMe != null, isHasQuotes);
+ break;
+ case PICTURE:
+ message = new PictureMessage(id, html);
+ break;
+ case FILE:
+ message = new FileMessage(id, html);
+ break;
+ case VIDEO:
+ message = new VideoMessage(id, html);
+ break;
+ case CONTACT:
+ message = new ContactMessage(id, html);
+ break;
+ case MOJI:
+ message = new MojiMessage(id, html);
+ break;
+ case UNKNOWN:
+ message = new UnknownMessage(id, html);
+ break;
+ default:
+ break;
+ }
+ MessageEvent eventType = editId == null ? MessageEvent.RECEIVED : isRemoved ? MessageEvent.REMOVED : MessageEvent.EDITED;
+ if (receiver instanceof Group) {
+ skype.doGroupMessageEvent(eventType, (Group) receiver, (User) sender, message);
+ } else {
+ skype.doUserMessageEvent(eventType, (User) sender, message);
+ }
+ } else {
+ switch (messageType) {
+ case "ThreadActivity/AddMember":
+ List usernames = getXMLFields(formatted.body, "target");
+ skype.usersAddedToGroup(usernames.stream().map(username -> (User) parseEntity(username)).collect(Collectors.toList()), (Group) sender);
+ break;
+ case "ThreadActivity/DeleteMember":
+ usernames = getXMLFields(formatted.body, "target");
+ skype.usersAddedToGroup(usernames.stream().map(username -> (User) parseEntity(username)).collect(Collectors.toList()), (Group) sender);
+ break;
+ case "ThreadActivity/TopicUpdate":
+ skype.groupTopicChanged((Group) sender, getPlaintext(getXMLField(formatted.body, "value")));
+ break;
+ case "ThreadActivity/RoleUpdate":
+ Document doc = getDocument(formatted.body);
+ NodeList targetNodes = doc.getElementsByTagName("target");
+ List> roles = new ArrayList<>(targetNodes.getLength());
+ for (int i = 0; i < targetNodes.getLength(); i++) {
+ Node targetNode = targetNodes.item(i);
+ User user = null;
+ Role role = null;
+ for (int j = 0; j < targetNode.getChildNodes().getLength(); j++) {
+ Node targetPropertyNode = targetNode.getChildNodes().item(j);
+ if (targetPropertyNode.getNodeName().equals("id")) {
+ user = (User) parseEntity(targetPropertyNode.getTextContent());
+ skype.updateUser(user);
+ } else if (targetPropertyNode.getNodeName().equals("role")) {
+ role = Role.getRole(targetPropertyNode.getTextContent());
+ }
+ }
+ if (user != null && role != null) {
+ roles.add(new Pair<>(user, role));
}
}
- if (user != null && role != null) {
- roles.add(new Pair<>(user, role));
- }
- }
- skype.usersRolesChanged((Group) sender, roles);
- break;
- default:
- break;
+ skype.usersRolesChanged((Group) sender, roles);
+ break;
+ default:
+ break;
+ }
}
+
}
break;
case "NFY":
@@ -494,34 +550,49 @@ public void connect() throws IOException, InterruptedException {
pingThread.start();
}
+ public void sendMessage(Chat chat, Message message) throws IOException {
+ String entity = "";
+ switch (chat.getType()) {
+ case GROUP:
+ entity = chat.getIdentity();
+ break;
+ case USER:
+ entity = "8:" + chat.getIdentity();
+ break;
+ }
+ sendMessage(entity, message);
+ }
+
+ @Deprecated
public void sendUserMessage(User user, String message) throws IOException {
sendMessage("8:" + user.getUsername(), getSanitized(message));
}
+ @Deprecated
public void sendGroupMessage(Group group, String message) throws IOException {
- sendMessage("19:" + group.getId() + "@thread.skype", getSanitized(message));
+ sendMessage(group.getId(), getSanitized(message));
}
public void addUserToGroup(User user, Role role, Group group) throws IOException {
- String body = String.format("19:%s@thread.skype8:%s%s",
+ String body = String.format("%s8:%s%s",
group.getId(), user.getUsername(), role.getRoleString());
sendPacket("PUT", "MSGR\\THREAD", body);
}
public void removeUserFromGroup(User user, Group group) throws IOException {
- String body = String.format("19:%s@thread.skype8:%s", group.getId(),
+ String body = String.format("%s8:%s", group.getId(),
user.getUsername());
sendPacket("DEL", "MSGR\\THREAD", body);
}
public void changeGroupTopic(Group group, String topic) throws IOException {
String body =
- String.format("19:%s@thread.skype%s", group.getId(), getSanitized(topic));
+ String.format("%s%s", group.getId(), getSanitized(topic));
sendPacket("PUT", "MSGR\\THREAD", body);
}
public void changeUserRole(User user, Role role, Group group) throws IOException {
- String body = String.format("19:%s@thread.skype8:%s%s",
+ String body = String.format("%s8:%s%s",
group.getId(), user.getUsername(), role.getRoleString());
sendPacket("PUT", "MSGR\\THREAD", body);
}
@@ -533,12 +604,47 @@ public void changePresence(Presence presence) throws IOException {
sendPacket("PUT", "MSGR\\PRESENCE", formattedPublicationMessage);
}
+ @Deprecated
private void sendMessage(String entity, String message) throws IOException {
String body = FormattedMessage.format("8:" + username + ";epid={" + EPID + "}", entity, "Messaging: 2.0", message,
"Content-Type: application/user+xml", "Message-Type: RichText");
sendPacket("SDG", "MSGR", body);
}
+ private void sendMessage(String entity, Message message) throws IOException {
+ List headers = new ArrayList<>(Arrays.asList("Content-Type: application/user+xml", "Message-Type: " + MessageType.getTypeByClass(message).getHeaderType()));
+ if (message.getId() != null) {
+ headers.add("Skype-EditedId: " + message.getId());
+ }
+ AbstractMessage abstractMessage = (AbstractMessage) message;
+ String body = "";
+ switch (message.getType()) {
+ case TEXT:
+ TextMessage textMessage = (TextMessage) message;
+ body = FormattedMessage.format("8:" + username + ";epid={" + EPID + "}", entity, "Messaging: 2.0", getSanitized(textMessage.getHtml()),
+ headers.toArray(new String[headers.size()]));
+ sendPacket("SDG", "MSGR", body);
+ break;
+ case PICTURE:
+ case FILE:
+ case VIDEO:
+ case CONTACT:
+ case MOJI:
+ if (abstractMessage.isEmpty()) {
+ body = FormattedMessage.format("8:" + username + ";epid={" + EPID + "}", entity, "Messaging: 2.0", "",
+ headers.toArray(new String[headers.size()]));
+ sendPacket("SDG", "MSGR", body);
+ } else {
+ //Send this type of message not implemented yet;
+ }
+ break;
+ case UNKNOWN:
+ logger.warning("Strange UNKNOWN message has been tried to send.");
+ break;
+ }
+
+ }
+
public synchronized void disconnect() {
logger.finer("Stopping notification connector");
try {
@@ -639,25 +745,11 @@ private Object parseEntity(String rawEntity) {
logger.finest("Parsing entity " + rawEntity);
int senderBegin = rawEntity.indexOf(':');
int network = Integer.parseInt(rawEntity.substring(0, senderBegin));
- int end0 = rawEntity.indexOf('@');
- int end1 = rawEntity.indexOf(';');
- if (end0 == -1) {
- end0 = Integer.MAX_VALUE;
- }
- if (end1 == -1) {
- end1 = Integer.MAX_VALUE;
- }
- int senderEnd = Math.min(end0, end1);
- String name;
- if (senderEnd == Integer.MAX_VALUE) {
- name = rawEntity.substring(senderBegin + 1);
- } else {
- name = rawEntity.substring(senderBegin + 1, senderEnd);
- }
if (network == 8) {
- return skype.getUser(name);
+ int i = rawEntity.indexOf(';');
+ return skype.getUser(rawEntity.substring(2, i == -1 ? rawEntity.length() : i));
} else if (network == 19) {
- return skype.getGroup(name);
+ return skype.getGroup(rawEntity);
} else {
logger.warning("Error while parsing entity " + rawEntity + ": unknown network:" + network);
throw new IllegalArgumentException();
diff --git a/src/main/java/fr/delthas/skype/Skype.java b/src/main/java/fr/delthas/skype/Skype.java
index 9bbc451..d39cabc 100644
--- a/src/main/java/fr/delthas/skype/Skype.java
+++ b/src/main/java/fr/delthas/skype/Skype.java
@@ -1,5 +1,9 @@
package fr.delthas.skype;
+import fr.delthas.skype.MessageListener.MessageEvent;
+import fr.delthas.skype.message.AbstractMessage;
+import fr.delthas.skype.message.Message;
+
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -10,11 +14,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.logging.FileHandler;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.logging.SimpleFormatter;
+import java.util.logging.*;
import java.util.stream.Collectors;
/**
@@ -27,7 +27,6 @@
* LF which will be replaced with CRLF if needed.
*
* If you want to report a bug, please enable debug/logs with {@link Skype#setDebug(Path)}, before using {@link #connect()}.
- *
*/
public final class Skype {
@@ -78,9 +77,8 @@ public Skype(String username, String password) {
/**
* Calls {@code connect(Presence.CONNECTED)}.
*
- * @throws IOException If an error is thrown while connecting.
+ * @throws IOException If an error is thrown while connecting.
* @throws InterruptedException If the connection is interrupted.
- *
* @see #connect(Presence)
*/
public void connect() throws IOException, InterruptedException {
@@ -92,8 +90,7 @@ public void connect() throws IOException, InterruptedException {
* Connects the Skype interface. Will block until connected.
*
* @param presence The initial presence of the Skype account after connection. Cannot be {@link Presence#OFFLINE}.
- *
- * @throws IOException If an error is thrown while connecting.
+ * @throws IOException If an error is thrown while connecting.
* @throws InterruptedException If the connection is interrupted.
*/
public void connect(Presence presence) throws IOException, InterruptedException {
@@ -172,7 +169,6 @@ public User getSelf() {
/**
* @return The current list of contacts of the account (snapshot, won't be updated).
- *
*/
public List getContacts() {
ensureConnected();
@@ -211,9 +207,8 @@ public boolean isConnected() {
* Enables or disables debug of the Skype library (globally). (By default logs are disabled.)
*
* If enabled, debug information and logs will be written to a log file at the specified path. If the path is null, the debug will be disabled.
- *
+ *
* @param path The path at which to write debugging information, or null to disable logging.
- *
* @throws IOException may be thrown when adding a file handler to the logger
*/
public static void setDebug(Path path) throws IOException {
@@ -376,6 +371,7 @@ void declineContactRequest(ContactRequest contactRequest) {
// --- Package-private methods that simply call the notification connector --- //
+ @Deprecated
void sendUserMessage(User user, String message) {
ensureConnected();
try {
@@ -386,6 +382,51 @@ void sendUserMessage(User user, String message) {
}
}
+ public enum MessageAction {
+ SEND,
+ EDIT,
+ REMOVE
+ }
+
+ /**
+ * Execute action with message.
+ * Message can be send, edit and removed
+ *
+ * @param chat where will be send action
+ * @param message object of some message type
+ * @param action send, edit or removed
+ */
+ void doMessageAction(Chat chat, Message message, MessageAction action) {
+ ensureConnected();
+ try {
+ logger.finer("Do " + action.name() + "-action in chat: " + chat + " message: " + message);
+ switch (action) {
+ case SEND:
+ notifConnector.sendMessage(chat, message);
+ break;
+ case EDIT:
+ if (message.getId() != null) {
+ notifConnector.sendMessage(chat, message);
+ } else {
+ logger.warning("Message has not id. Can't edit message: " + message);
+ }
+ break;
+ case REMOVE:
+ if (message.getId() != null) {
+ AbstractMessage abstractMessage = (AbstractMessage) message;
+ abstractMessage.setHtml("");
+ notifConnector.sendMessage(chat, message);
+ } else {
+ logger.warning("Message has not id. Can't remove message: " + message);
+ }
+ break;
+ }
+ } catch (IOException e) {
+ error(e);
+ }
+ }
+
+ @Deprecated
void sendGroupMessage(Group group, String message) {
ensureConnected();
try {
@@ -438,6 +479,7 @@ void changeGroupTopic(Group group, String topic) {
// --- Listeners call methods --- //
+ @Deprecated
void userMessageReceived(User sender, String message) {
updateUser(sender);
logger.finer("Received message: " + message + " from user: " + sender);
@@ -446,6 +488,24 @@ void userMessageReceived(User sender, String message) {
}
}
+ void doUserMessageEvent(MessageEvent event, User sender, T message) {
+ logger.finer("Event: " + event + ", message('" + message.getType().getName() + "'): " + message + " from user: " + sender);
+ for (UserMessageListener listener : userMessageListeners) {
+ switch (event) {
+ case RECEIVED:
+ listener.messageReceived(sender, message);
+ break;
+ case EDITED:
+ listener.messageEdited(sender, message);
+ break;
+ case REMOVED:
+ listener.messageRemoved(sender, message);
+ break;
+ }
+ }
+ }
+
+ @Deprecated
void groupMessageReceived(Group group, User sender, String message) {
logger.finer("Received group message: " + message + " from user: " + sender + " in group: " + group);
for (GroupMessageListener listener : groupMessageListeners) {
@@ -453,6 +513,23 @@ void groupMessageReceived(Group group, User sender, String message) {
}
}
+ void doGroupMessageEvent(MessageEvent event, Group group, User sender, T message) {
+ logger.finer("Event: " + event + ", message('" + message.getType().getName() + "'): " + message + " from user: " + sender + " in group: " + group);
+ for (GroupMessageListener listener : groupMessageListeners) {
+ switch (event) {
+ case RECEIVED:
+ listener.messageReceived(group, sender, message);
+ break;
+ case EDITED:
+ listener.messageEdited(group, sender, message);
+ break;
+ case REMOVED:
+ listener.messageRemoved(group, sender, message);
+ break;
+ }
+ }
+ }
+
void userPresenceChanged(User user, Presence oldPresence, Presence presence) {
logger.finer("User: " + user + " changed presence from: " + oldPresence + " to: " + presence);
for (UserPresenceListener listener : userPresenceListeners) {
diff --git a/src/main/java/fr/delthas/skype/User.java b/src/main/java/fr/delthas/skype/User.java
index da31e7a..9d651e3 100644
--- a/src/main/java/fr/delthas/skype/User.java
+++ b/src/main/java/fr/delthas/skype/User.java
@@ -1,13 +1,14 @@
package fr.delthas.skype;
+import fr.delthas.skype.message.Message;
+
/**
* A Skype account.
*
* All information will be updated as updates are received (this object is NOT an immutable view/snapshot of a user account).
- *
*/
-public class User {
+public class User implements Chat {
private final Skype skype;
private final String username;
@@ -26,7 +27,6 @@ public class User {
/**
* Blocks this user (without reporting the account).
- *
*/
public void block() {
skype.block(this);
@@ -34,7 +34,6 @@ public void block() {
/**
* Unblocks this user.
- *
*/
public void unblock() {
skype.unblock(this);
@@ -51,7 +50,6 @@ public void sendContactRequest(String greeting) {
/**
* Removes this user from the list of contacts of the Skype account. If the user isn't a contact, nothing happens.
- *
*/
public void removeFromContacts() {
skype.removeFromContacts(this);
@@ -62,10 +60,26 @@ public void removeFromContacts() {
*
* @param message The message to send to this user.
*/
+ @Deprecated
public void sendMessage(String message) {
skype.sendUserMessage(this, message);
}
+ @Override
+ public void sendMessage(Message message) {
+ skype.doMessageAction(this, message, Skype.MessageAction.SEND);
+ }
+
+ @Override
+ public void editMessage(Message message) {
+ skype.doMessageAction(this, message, Skype.MessageAction.EDIT);
+ }
+
+ @Override
+ public void removeMessage(Message message) {
+ skype.doMessageAction(this, message, Skype.MessageAction.REMOVE);
+ }
+
/**
* @return The username of this user.
*/
@@ -228,4 +242,18 @@ public String toString() {
return "User: " + getUsername();
}
+ @Override
+ public Skype getSkype() {
+ return skype;
+ }
+
+ @Override
+ public String getIdentity() {
+ return username;
+ }
+
+ @Override
+ public ChatType getType() {
+ return ChatType.USER;
+ }
}
diff --git a/src/main/java/fr/delthas/skype/UserMessageListener.java b/src/main/java/fr/delthas/skype/UserMessageListener.java
index 01c9ce5..732e8f2 100644
--- a/src/main/java/fr/delthas/skype/UserMessageListener.java
+++ b/src/main/java/fr/delthas/skype/UserMessageListener.java
@@ -1,18 +1,29 @@
package fr.delthas.skype;
+import fr.delthas.skype.message.Message;
+
/**
* A listener for new messages sent to a Skype account.
- *
*/
-public interface UserMessageListener {
+public interface UserMessageListener extends MessageListener {
/**
* Called when a message is sent from a user to the Skype account while it is connected.
*
- * @param sender The sender of the message.
+ * @param sender The sender of the message.
* @param message The message sent.
*/
- public void messageReceived(User sender, String message);
+ @Deprecated
+ default void messageReceived(User sender, String message) {
+ }
+
+ default void messageReceived(User sender, Message message) {
+ }
+
+ default void messageEdited(User sender, Message message) {
+ }
+ default void messageRemoved(User sender, Message message) {
+ }
}
diff --git a/src/main/java/fr/delthas/skype/WebConnector.java b/src/main/java/fr/delthas/skype/WebConnector.java
index c69b722..02e8036 100644
--- a/src/main/java/fr/delthas/skype/WebConnector.java
+++ b/src/main/java/fr/delthas/skype/WebConnector.java
@@ -115,9 +115,11 @@ private User updateUser(JSONObject userJSON, boolean newContactType) throws Pars
} else {
userUsername = userJSON.getString("id");
userDisplayName = userJSON.optString("display_name", null);
- JSONObject nameJSON = userJSON.getJSONObject("name");
- userFirstName = nameJSON.optString("first", null);
- userLastName = nameJSON.optString("surname", null);
+ if (userJSON.optString("type", "skype").equals("skype")) {
+ JSONObject nameJSON = userJSON.getJSONObject("name");
+ userFirstName = nameJSON.optString("first", null);
+ userLastName = nameJSON.optString("surname", null);
+ }
userMood = userJSON.optString("mood", null);
if (userJSON.has("locations")) {
JSONObject locationJSON = userJSON.optJSONArray("locations").optJSONObject(0);
diff --git a/src/main/java/fr/delthas/skype/message/AbstractMessage.java b/src/main/java/fr/delthas/skype/message/AbstractMessage.java
new file mode 100644
index 0000000..65c6b13
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/AbstractMessage.java
@@ -0,0 +1,67 @@
+package fr.delthas.skype.message;
+
+import org.jsoup.Jsoup;
+
+/**
+ * Abstract class for all type of message with all common field and methods
+ */
+public abstract class AbstractMessage implements Message {
+
+ private String id;
+ private MessageType type;
+ private String html;
+ private String text;
+
+
+ public AbstractMessage(String id, String html) {
+ this.id = id;
+ this.html = html;
+ this.text = Jsoup.parseBodyFragment(html, "").text();
+ this.type = MessageType.getTypeByClass(getClass().getSimpleName());
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public MessageType getType() {
+ return type;
+ }
+
+ protected void setType(MessageType type) {
+ this.type = type;
+ }
+
+ public String getHtml() {
+ return html;
+ }
+
+ public void setHtml(String html) {
+ this.html = html;
+ this.text = Jsoup.parseBodyFragment(html, "").text();
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void empty() {
+ setHtml("");
+ }
+
+ public boolean isEmpty() {
+ return html.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " {" +
+ "id='" + id + '\'' +
+ ", type=" + type +
+ ", html='" + html + '\'' +
+ ", text='" + text + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/message/ContactMessage.java b/src/main/java/fr/delthas/skype/message/ContactMessage.java
new file mode 100644
index 0000000..a97abd6
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/ContactMessage.java
@@ -0,0 +1,12 @@
+package fr.delthas.skype.message;
+
+/**
+ * Message with contact
+ */
+public class ContactMessage extends AbstractMessage {
+
+ public ContactMessage(String id, String html) {
+ super(id, html);
+ }
+
+}
diff --git a/src/main/java/fr/delthas/skype/message/FileMessage.java b/src/main/java/fr/delthas/skype/message/FileMessage.java
new file mode 100644
index 0000000..e07c5ae
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/FileMessage.java
@@ -0,0 +1,10 @@
+package fr.delthas.skype.message;
+
+/**
+ * Message with file
+ */
+public class FileMessage extends AbstractMessage {
+ public FileMessage(String id, String html) {
+ super(id, html);
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/message/Message.java b/src/main/java/fr/delthas/skype/message/Message.java
new file mode 100644
index 0000000..2b339b1
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/Message.java
@@ -0,0 +1,16 @@
+package fr.delthas.skype.message;
+
+/**
+ * Interface for messages`
+ */
+public interface Message {
+
+ public static String X_URL_THUMBNAIL = "/URIObject/@url_thumbnail";
+ public static String X_URL = "/URIObject/@uri";
+ public static String X_TYPE = "/URIObject/@type";
+ public static String X_ORIGINAL_NAME = "/URIObject/OriginalName/@v";
+
+ String getId();
+
+ MessageType getType();
+}
diff --git a/src/main/java/fr/delthas/skype/message/MessageType.java b/src/main/java/fr/delthas/skype/message/MessageType.java
new file mode 100644
index 0000000..7480d83
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/MessageType.java
@@ -0,0 +1,65 @@
+package fr.delthas.skype.message;
+
+import static fr.delthas.skype.Constants.*;
+
+/**
+ * Skype message types
+ */
+public enum MessageType {
+ TEXT("Text", HEADER_RICH_TEXT, TextMessage.class),
+ PICTURE("Picture", HEADER_PICTURE, PictureMessage.class),
+ FILE("File", HEADER_FILE, FileMessage.class),
+ VIDEO("Video", HEADER_VIDEO, VideoMessage.class),
+ CONTACT("Contact", HEADER_CONTACT, ContactMessage.class),
+ MOJI("Moji", HEADER_MOJI, MojiMessage.class),
+ UNKNOWN("Unknown", "Unknown", UnknownMessage.class);
+
+ private String name;
+ private String headerType;
+ private Class extends Message> clazz;
+
+ MessageType(String name, String headerType, Class extends Message> clazz) {
+ this.name = name;
+ this.headerType = headerType;
+ this.clazz = clazz;
+ }
+
+ public String getHeaderType() {
+ return headerType;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Class extends Message> getClazz() {
+ return clazz;
+ }
+
+ public static MessageType getTypeByClass(String className) {
+ for (MessageType type : MessageType.values()) {
+ if (type.getClazz().getSimpleName().equals(className)) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public static MessageType getTypeByClass(Message message) {
+ if (message != null) {
+ String className = message.getClass().getSimpleName();
+ return getTypeByClass(className);
+ }
+ return UNKNOWN;
+ }
+
+ public static MessageType getTypeByHeaderType(String headerType) {
+ if (HEADER_TEXT.equals(headerType)) return TEXT;
+ for (MessageType type : MessageType.values()) {
+ if (type.getHeaderType().equals(headerType)) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/message/MojiMessage.java b/src/main/java/fr/delthas/skype/message/MojiMessage.java
new file mode 100644
index 0000000..4893530
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/MojiMessage.java
@@ -0,0 +1,12 @@
+package fr.delthas.skype.message;
+
+/**
+ * Message with moji
+ */
+public class MojiMessage extends AbstractMessage {
+
+ public MojiMessage(String id, String html) {
+ super(id, html);
+ }
+
+}
diff --git a/src/main/java/fr/delthas/skype/message/PictureMessage.java b/src/main/java/fr/delthas/skype/message/PictureMessage.java
new file mode 100644
index 0000000..44a08ff
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/PictureMessage.java
@@ -0,0 +1,11 @@
+package fr.delthas.skype.message;
+
+/**
+ * Message with picture
+ */
+public class PictureMessage extends AbstractMessage {
+
+ public PictureMessage(String id, String html) {
+ super(id, html);
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/message/TextMessage.java b/src/main/java/fr/delthas/skype/message/TextMessage.java
new file mode 100644
index 0000000..1da6db0
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/TextMessage.java
@@ -0,0 +1,43 @@
+package fr.delthas.skype.message;
+
+/**
+ * Simple text message
+ */
+public class TextMessage extends AbstractMessage {
+ private boolean isMe;
+ private boolean hasQuotes;
+
+ public TextMessage(String id, String html) {
+ super(id, html);
+ }
+
+ public TextMessage(String id, String html, boolean isMe) {
+ this(id, html);
+ this.isMe = isMe;
+ }
+
+ public TextMessage(String id, String html, boolean isMe, boolean hasQuotes) {
+ this(id, html, isMe);
+ this.hasQuotes = hasQuotes;
+ }
+
+ public boolean hasQuotes() {
+ return hasQuotes;
+ }
+
+ public void setHasQuotes(boolean hasQuotes) {
+ this.hasQuotes = hasQuotes;
+ }
+
+ public boolean isMe() {
+ return isMe;
+ }
+
+ @Override
+ public String toString() {
+ return "TextMessage{" +
+ "isMe=" + isMe +
+ ", hasQuotes=" + hasQuotes +
+ "} " + super.toString();
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/message/UnknownMessage.java b/src/main/java/fr/delthas/skype/message/UnknownMessage.java
new file mode 100644
index 0000000..89cc9cc
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/UnknownMessage.java
@@ -0,0 +1,11 @@
+package fr.delthas.skype.message;
+
+/**
+ * Message with unknown type
+ */
+public class UnknownMessage extends AbstractMessage {
+
+ public UnknownMessage(String id, String html) {
+ super(id, html);
+ }
+}
diff --git a/src/main/java/fr/delthas/skype/message/VideoMessage.java b/src/main/java/fr/delthas/skype/message/VideoMessage.java
new file mode 100644
index 0000000..5b88d76
--- /dev/null
+++ b/src/main/java/fr/delthas/skype/message/VideoMessage.java
@@ -0,0 +1,11 @@
+package fr.delthas.skype.message;
+
+/**
+ * Message with video
+ */
+public class VideoMessage extends AbstractMessage {
+
+ public VideoMessage(String id, String html) {
+ super(id, html);
+ }
+}
diff --git a/src/test/java/fr/delthas/skype/TestListeners.java b/src/test/java/fr/delthas/skype/TestListeners.java
new file mode 100644
index 0000000..7617b45
--- /dev/null
+++ b/src/test/java/fr/delthas/skype/TestListeners.java
@@ -0,0 +1,68 @@
+package fr.delthas.skype;
+
+import fr.delthas.skype.message.Message;
+import fr.delthas.skype.message.TextMessage;
+import org.junit.Assert;
+
+import java.util.logging.Logger;
+
+@SuppressWarnings({"javadoc", "static-method"})
+public class TestListeners {
+
+ public static Logger logger = Logger.getLogger("TestConnect");
+
+ public void testConnect() {
+ Skype skype = new Skype("username", "password");
+ try {
+ logger.info("Starting...");
+ skype.connect();
+ logger.info("Started");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(e.getMessage());
+ }
+ skype.setErrorListener(e -> {
+ e.printStackTrace();
+ Assert.fail(e.getMessage());
+ });
+ skype.addGroupMessageListener(new GroupMessageListener() {
+ @Override
+ public void messageReceived(Group group, User sender, Message message) {
+ logger.info("Received " + message);
+ }
+
+ @Override
+ public void messageEdited(Group group, User sender, Message message) {
+ logger.info("Edited " + message);
+ }
+
+ @Override
+ public void messageRemoved(Group group, User sender, Message message) {
+ logger.info("Removed " + message);
+ }
+ });
+
+ skype.addUserMessageListener(new UserMessageListener() {
+ @Override
+ public void messageReceived(User sender, Message message) {
+ logger.info("Received " + message);
+ }
+
+ @Override
+ public void messageEdited(User sender, Message message) {
+ logger.info("Edited " + message);
+ }
+
+ @Override
+ public void messageRemoved(User sender, Message message) {
+ logger.info("Removed " + message);
+ }
+ });
+
+ }
+
+ public static void main(String... strings) {
+ new TestListeners().testConnect();
+ }
+
+}
diff --git a/src/test/java/fr/delthas/skype/message/TestMessage.java b/src/test/java/fr/delthas/skype/message/TestMessage.java
new file mode 100644
index 0000000..c135de5
--- /dev/null
+++ b/src/test/java/fr/delthas/skype/message/TestMessage.java
@@ -0,0 +1,74 @@
+package fr.delthas.skype.message;
+
+import fr.delthas.skype.ParseException;
+import org.apache.commons.io.IOUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.junit.Test;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class of fr.delthas.skype
+ *
+ * @author aivanov
+ */
+public class TestMessage {
+
+ private DocumentBuilder documentBuilder;
+
+
+ @Test
+ public void test() throws Exception {
+ documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+
+ String xml = getXml("fr/delthas/skype/message/picture.xml");
+ System.out.println(getXMLField(xml, "URIObject"));
+ }
+
+ private static Document getPlaintext(String string) {
+ return Jsoup.parseBodyFragment(string);
+ }
+
+ public static String getXml(String path) throws IOException {
+ return IOUtils.toString(ClassLoader.getSystemResourceAsStream(path), "UTF-8");
+ }
+
+
+ private org.w3c.dom.Document getDocument(String XML) throws ParseException {
+ try {
+ return documentBuilder.parse(new InputSource(new StringReader(XML)));
+ } catch (IOException | SAXException e) {
+ throw new ParseException(e);
+ }
+ }
+
+ private List getXMLFields(String XML, String fieldName) throws ParseException {
+ NodeList nodes = getDocument(XML).getElementsByTagName(fieldName);
+ List fields = new ArrayList<>(nodes.getLength());
+ for (int i = 0; i < nodes.getLength(); i++) {
+ fields.add(nodes.item(i).getTextContent());
+ }
+ return fields;
+ }
+
+ private String getXMLField(String XML, String fieldName) throws ParseException {
+ List fields = getXMLFields(XML, fieldName);
+ if (fields.size() > 1) {
+ throw new ParseException();
+ }
+ if (fields.size() == 0) {
+ return null;
+ }
+ return fields.get(0);
+ }
+
+}
diff --git a/src/test/resources/fr/delthas/skype/message/contact.xml b/src/test/resources/fr/delthas/skype/message/contact.xml
new file mode 100644
index 0000000..85d61bf
--- /dev/null
+++ b/src/test/resources/fr/delthas/skype/message/contact.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/fr/delthas/skype/message/file.xml b/src/test/resources/fr/delthas/skype/message/file.xml
new file mode 100644
index 0000000..3c574b2
--- /dev/null
+++ b/src/test/resources/fr/delthas/skype/message/file.xml
@@ -0,0 +1,12 @@
+
+
+ To view this shared file, go to:
+ hosts
+ hosts
+
+ https://login.skype.com/login/sso?go=webclient.xmm&docid=0-weu-d1-39307d971c65d7d697876272123304e4
+
+
+
+
diff --git a/src/test/resources/fr/delthas/skype/message/moji.xml b/src/test/resources/fr/delthas/skype/message/moji.xml
new file mode 100644
index 0000000..65a2ec6
--- /dev/null
+++ b/src/test/resources/fr/delthas/skype/message/moji.xml
@@ -0,0 +1,11 @@
+
+
+
+ Moji
+
+ https://login.skype.com/login/sso?go=xmmfallback&2b3bd2ed622e443c8ff98c5df55b4e31
+
+
+
+
diff --git a/src/test/resources/fr/delthas/skype/message/picture.xml b/src/test/resources/fr/delthas/skype/message/picture.xml
new file mode 100644
index 0000000..ee1559f
--- /dev/null
+++ b/src/test/resources/fr/delthas/skype/message/picture.xml
@@ -0,0 +1,10 @@
+
+To view this shared
+ photo, go to:
+
+ https://login.skype.com/login/sso?go=xmmfallback?pic=0-weu-d1-347eba640ba729af0d46f4acb3deffce
+
+
+
+
diff --git a/src/test/resources/fr/delthas/skype/message/ss.xml b/src/test/resources/fr/delthas/skype/message/ss.xml
new file mode 100644
index 0000000..e86667d
--- /dev/null
+++ b/src/test/resources/fr/delthas/skype/message/ss.xml
@@ -0,0 +1 @@
+(goldmedal)
\ No newline at end of file
diff --git a/src/test/resources/fr/delthas/skype/message/video.xml b/src/test/resources/fr/delthas/skype/message/video.xml
new file mode 100644
index 0000000..b01f189
--- /dev/null
+++ b/src/test/resources/fr/delthas/skype/message/video.xml
@@ -0,0 +1,10 @@
+To view this video
+message, go to:
+Video Message
+Video Message
+
+ https://login.skype.com/login/sso?go=xmmfallback&vim=0-neu-d1-d22932af94c973a48a944f983da278cd
+
+
+
\ No newline at end of file