Skip to content

Instantly share code, notes, and snippets.

@arlolra
Last active August 29, 2015 14:02
Show Gist options
  • Save arlolra/c6ee91363f22e354498b to your computer and use it in GitHub Desktop.
Save arlolra/c6ee91363f22e354498b to your computer and use it in GitHub Desktop.
diff --git a/chat/components/public/prplIConversation.idl b/chat/components/public/prplIConversation.idl
--- a/chat/components/public/prplIConversation.idl
+++ b/chat/components/public/prplIConversation.idl
@@ -6,16 +6,17 @@
#include "nsISupports.idl"
#include "nsISimpleEnumerator.idl"
#include "nsIObserver.idl"
interface imIAccountBuddy;
interface imIAccount;
interface nsIURI;
interface nsIDOMDocument;
+interface prplIMessage;
/*
* This is the XPCOM purple conversation component, a proxy for PurpleConversation.
*/
[scriptable, uuid(e40dc3e5-c9ff-457b-a6cc-655cce81042c)]
interface prplIConversation: nsISupports {
@@ -57,18 +58,29 @@ interface prplIConversation: nsISupports
/* When the conversation is closed from the UI. */
void close();
/* Method to add or remove an observer */
void addObserver(in nsIObserver aObserver);
void removeObserver(in nsIObserver aObserver);
- /* Observers will be all receive new-text notifications.
- aSubject will contain the message (prplIMessage) */
+ /* Observers will be notified of messaging events.
+ * aSubject will contain the message (prplIMessage)
+ *
+ * Fired notifications:
+ * new-text
+ * Tells the UI to display the message in the conversation.
+ * sending-message
+ * Where the message can be modified, or cancelled, by observers.
+ * receiving-message
+ * Observers can modify the message, or stop its processing.
+ */
+ void notifyObservers(in prplIMessage aSubject, in string aTopic,
+ [optional] in wstring aData);
};
[scriptable, uuid(0c072a80-103a-4992-b249-8e442b5f0d46)]
interface prplIConvIM: prplIConversation {
/* The buddy at the remote end of the conversation */
readonly attribute imIAccountBuddy buddy;
diff --git a/chat/components/public/prplIMessage.idl b/chat/components/public/prplIMessage.idl
--- a/chat/components/public/prplIMessage.idl
+++ b/chat/components/public/prplIMessage.idl
@@ -21,17 +21,17 @@ interface prplIMessageAction: nsIRunnabl
[scriptable, uuid(d9f0ca7f-ee59-4657-a3dd-f458c204ca45)]
interface prplIMessage: nsISupports {
/* The uniqueness of the message id is only guaranteed across
messages of a conversation, not across all messages created
during the execution of the application. */
readonly attribute unsigned long id;
readonly attribute AUTF8String who;
readonly attribute AUTF8String alias;
- readonly attribute AUTF8String originalMessage;
+ attribute AUTF8String originalMessage;
attribute AUTF8String message;
readonly attribute AUTF8String iconURL;
readonly attribute PRTime time;
readonly attribute prplIConversation conversation;
/* Holds the sender color for Chats.
Empty string by default, it is set by the conversation binding. */
attribute AUTF8String color;
@@ -51,17 +51,17 @@ interface prplIMessage: nsISupports {
internal UI purposes
(e.g. for contact-aware
conversions). */
/* PURPLE_MESSAGE_NICK = 0x0020, /**< Contains your nick. */
readonly attribute boolean containsNick;
/* PURPLE_MESSAGE_NO_LOG = 0x0040, /**< Do not log. */
readonly attribute boolean noLog;
/* PURPLE_MESSAGE_ERROR = 0x0200, /**< Error message. */
- readonly attribute boolean error;
+ attribute boolean error;
/* PURPLE_MESSAGE_DELAYED = 0x0400, /**< Delayed message. */
readonly attribute boolean delayed;
/* PURPLE_MESSAGE_RAW = 0x0800, /**< "Raw" message - don't
apply formatting */
readonly attribute boolean noFormat;
/* PURPLE_MESSAGE_IMAGES = 0x1000, /**< Message contains images */
readonly attribute boolean containsImages;
/* PURPLE_MESSAGE_NOTIFY = 0x2000, /**< Message is a notification */
diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConversations.js
--- a/chat/components/src/imConversations.js
+++ b/chat/components/src/imConversations.js
@@ -16,16 +16,17 @@ XPCOMUtils.defineLazyGetter(this, "bundl
);
function UIConversation(aPrplConversation)
{
this._prplConv = {};
this.id = ++gLastUIConvId;
this._observers = [];
this._messages = [];
+ this._msgBuffer = [];
this.changeTargetTo(aPrplConversation);
let iface = Ci["prplIConv" + (aPrplConversation.isChat ? "Chat" : "IM")];
this._interfaces = this._interfaces.concat(iface);
// XPConnect will create a wrapper around 'this' after here,
// so the list of exposed interfaces shouldn't change anymore.
this.updateContactObserver();
Services.obs.notifyObservers(this, "new-ui-conversation", null);
}
@@ -261,16 +262,21 @@ UIConversation.prototype = {
},
observeConv: function(aTargetId, aSubject, aTopic, aData) {
if (aTargetId != this._currentTargetId &&
(aTopic == "new-text" ||
(aTopic == "update-typing" &&
this._prplConv[aTargetId].typingState == Ci.prplIConvIM.TYPING)))
this.target = this._prplConv[aTargetId];
+
+ // Process new texts and possibly cancel the message at this point.
+ if (aTopic == "new-text" && !this.processText(aSubject))
+ return;
+
this.notifyObservers(aSubject, aTopic, aData);
if (aTopic == "new-text") {
Services.obs.notifyObservers(aSubject, aTopic, aData);
if (aSubject.incoming && !aSubject.system &&
(!this.isChat || aSubject.containsNick)) {
this.notifyObservers(aSubject, "new-directed-incoming-message", aData);
Services.obs.notifyObservers(aSubject, "new-directed-incoming-message", aData);
}
@@ -284,17 +290,81 @@ UIConversation.prototype = {
// prplIConversation
get isChat() this.target.isChat,
get account() this.target.account,
get name() this.target.name,
get normalizedName() this.target.normalizedName,
get title() this.target.title,
get startDate() this.target.startDate,
- sendMsg: function (aMsg) { this.target.sendMsg(aMsg); },
+
+ sendMsg: function (aMsg) {
+ // Create a new message with aMsg to satisfy the observer interface.
+ let nMsg = new Message(this.target.name, aMsg, { outgoing: true });
+
+ // Notify observers that now is the time to modify the message
+ // before sending. After modifications,
+ // "message" will contain the outgoing text
+ // "originalMessage" will be displayed to the user
+ this.target.notifyObservers(nMsg, "sending-message", null);
+
+ // If an observer cancelled the message, abort here.
+ if (nMsg.error)
+ return;
+
+ // Because of the libpurple context restriction, we're forced
+ // to buffer messages here. When "new-text" is fired, we'll
+ // grab this message to display the intended content.
+ this.bufferMsg(this.target, nMsg);
+
+ // Send just the outgoing text.
+ this.target.sendMsg(nMsg.message);
+ },
+
+ bufferMsg: function(aConv, aMsg) {
+ this._msgBuffer.push({ conv: aConv, msg: aMsg });
+ },
+
+ // If we don't find the message, it was injected directly
+ // and was intended to be ignored.
+ pluckMsg: function(aConv, aMsg) {
+ let mb = this._msgBuffer;
+ return mb.some(function(a, i) {
+ if (a.conv === aConv && a.msg.message === aMsg.message) {
+ // This is likely the message we stored.
+ // Replace the text to be displayed with its contents.
+ aMsg.originalMessage = a.msg.originalMessage;
+ // And remove it from the buffer.
+ mb.splice(i, 1);
+ // Indicate the message was located.
+ return true;
+ }
+ });
+ },
+
+ processText: function(aMsg) {
+ if (!aMsg.system) {
+ if (aMsg.outgoing) {
+ // If the message isn't located, suppress it.
+ return this.pluckMsg(this.target, aMsg);
+ } else if (aMsg.incoming) {
+ // Notify observers that now is the time to modify the incoming
+ // message. After modifications,
+ // "message" will still contain the incoming text
+ // "originalMessage" will be displayed to the user
+ this.target.notifyObservers(aMsg, "receiving-message", null);
+
+ // If an observer cancelled the message, abort here.
+ if (aMsg.error)
+ return false;
+ }
+ }
+ return true;
+ },
+
unInit: function() {
for each (let conv in this._prplConv)
gConversationsService.forgetConversation(conv);
if (this._observedContact) {
this._observedContact.removeObserver(this);
delete this._observedContact;
}
this._prplConv = {}; // Prevent .close from failing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment