Skip to content

Instantly share code, notes, and snippets.

@arlolra
Created July 3, 2014 10:45
Show Gist options
  • Save arlolra/9c9accd2da762705dba9 to your computer and use it in GitHub Desktop.
Save arlolra/9c9accd2da762705dba9 to your computer and use it in GitHub Desktop.
diff --git a/chat/components/public/imIConversationsService.idl b/chat/components/public/imIConversationsService.idl
--- a/chat/components/public/imIConversationsService.idl
+++ b/chat/components/public/imIConversationsService.idl
@@ -62,8 +62,26 @@ interface imIConversationsService: nsISu
imIConversation getUIConversationByContactId(in long aId);
nsISimpleEnumerator getConversations();
prplIConversation getConversationById(in unsigned long aId);
prplIConversation getConversationByNameAndAccount(in AUTF8String aName,
in imIAccount aAccount,
in boolean aIsChat);
};
+
+[scriptable, uuid(4391ba5c-9566-41a9-bb9b-fd0a0a490c2c)]
+interface imIOutgoingMessage: nsISupports {
+ attribute AUTF8String message;
+ attribute boolean cancelled;
+ readonly attribute imIConversation conversation;
+};
+
+[scriptable, uuid(0684ed1e-97b7-4eba-bf58-dd9dd81c668c)]
+interface imISendableMessage: nsISupports {
+ void getSendableMessages([optional] out unsigned long messageCount,
+ [retval, array, size_is(messageCount)] out AUTF8String messages);
+ void setSendableMessages(in unsigned long messageCount,
+ [array, size_is(messageCount)] in AUTF8String messages);
+ readonly attribute AUTF8String originalMessage;
+ attribute boolean cancelled;
+ readonly attribute prplIConversation target;
+};
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
@@ -4,16 +4,17 @@
#include "nsISupports.idl"
#include "nsISimpleEnumerator.idl"
#include "nsIObserver.idl"
interface imIAccountBuddy;
interface imIAccount;
+interface imIOutgoingMessage;
interface nsIURI;
interface nsIDOMDocument;
/*
* This is the XPCOM purple conversation component, a proxy for PurpleConversation.
*/
[scriptable, uuid(e40dc3e5-c9ff-457b-a6cc-655cce81042c)]
@@ -39,16 +40,18 @@ interface prplIConversation: nsISupports
readonly attribute PRTime startDate;
/* Unique identifier of the conversation */
/* Setable only once by purpleCoreService while calling addConversation. */
attribute unsigned long id;
/* Send a message in the conversation */
void sendMsg(in AUTF8String aMsg);
+ void prepareForSending(in imIOutgoingMessage message);
+
/* Send information about the current typing state to the server.
aString should contain the content currently in the text field. The
protocol should return the number of characters that can still be typed. */
long sendTyping(in AUTF8String aString);
const long NO_TYPING_LIMIT = 2147483647; // max int = 2 ^ 31 - 1
/* Un-initialize the conversation. Will be called by
purpleCoreService::RemoveConversation when the conversation is
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
@@ -284,17 +284,42 @@ 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) {
+ // add-ons (eg. pastebin) have an opportunity to cancel the message at this
+ // point, or change the text content of the message.
+ // If an add-on wants to split a message, it should truncate the first
+ // message, and insert new messages using the conversation's sendMsg method.
+ let om = new OutgoingMessage(aMsg, this);
+ this.notifyObservers(om, "preparing-message", null);
+ if (om.cancelled)
+ return;
+
+ // prpls have an opportunity here to preprocess messages before they are
+ // sent, eg. split long messages. If a message is split here, the split
+ // will be visible in the UI.
+ // prpls can return null if they don't need to make any change
+ let messages = this.target.prepareForSending(om) || [om.message];
+
+ for (let msg of messages) {
+ // add-ons (eg. OTR) have an opportunity to tweak or cancel the message
+ // point.
+ let sm = new SendableMessage(msg, this.target);
+ this.notifyObservers(sm, "sending-message", null);
+ if (sm.cancelled)
+ continue;
+ sm.getSendableMessages().forEach((m) => this.target.sendMsg(m));
+ }
+ },
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.
diff --git a/chat/modules/jsProtoHelper.jsm b/chat/modules/jsProtoHelper.jsm
--- a/chat/modules/jsProtoHelper.jsm
+++ b/chat/modules/jsProtoHelper.jsm
@@ -6,16 +6,18 @@ const EXPORTED_SYMBOLS = [
"GenericAccountPrototype",
"GenericAccountBuddyPrototype",
"GenericConvIMPrototype",
"GenericConvChatPrototype",
"GenericConvChatBuddyPrototype",
"GenericMessagePrototype",
"GenericProtocolPrototype",
"Message",
+ "OutgoingMessage",
+ "SendableMessage"
"TooltipInfo"
];
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
Cu.import("resource:///modules/imXPCOMUtils.jsm");
Cu.import("resource:///modules/imServices.jsm");
@@ -424,16 +426,33 @@ const GenericMessagePrototype = {
}
};
function Message(aWho, aMessage, aObject) {
this._init(aWho, aMessage, aObject);
}
Message.prototype = GenericMessagePrototype;
+function OutgoingMessage(message, conversation) {
+ this.message = message;
+ this.conversation = conversation;
+}
+OutgoingMessage.prototype = {
+ constructor: OutgoingMessage,
+ cancelled: false
+};
+
+function SendableMessage(message, target) {
+ this.orginalMessage = message;
+ this.target = target;
+}
+SendableMessage.prototype = {
+ constructor: SendableMessage,
+ cancelled: false
+};
const GenericConversationPrototype = {
__proto__: ClassInfo("prplIConversation", "generic conversation object"),
get wrappedJSObject() this,
get DEBUG() this._account.DEBUG,
get LOG() this._account.LOG,
get WARN() this._account.WARN,
@@ -467,17 +486,18 @@ const GenericConversationPrototype = {
try {
observer.observe(aSubject, aTopic, aData);
} catch(e) {
this.ERROR(e);
}
}
},
- sendMsg: function (aMsg) {
+ prepareForSending: function() null,
+ sendMsg: function(aMsg) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
sendTyping: function(aString) Ci.prplIConversation.NO_TYPING_LIMIT,
close: function() {
Services.obs.notifyObservers(this, "closing-conversation", null);
Services.conversations.removeConversation(this);
},
diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js
--- a/chat/protocols/irc/irc.js
+++ b/chat/protocols/irc/irc.js
@@ -129,19 +129,19 @@ const GenericIRCConversation = {
getMaxMessageLength: function() {
// Build the shortest possible message that could be sent to other users.
let baseMessage = ":" + this._account._nickname + this._account.prefix +
" " + this._account.buildMessage("PRIVMSG", this.name) +
" :\r\n";
return this._account.maxMessageLength -
this._account.countBytes(baseMessage);
},
- sendMsg: function(aMessage) {
+ prepareForSending: function(om) {
// Split the message by line breaks and send each one individually.
- let messages = aMessage.split(/[\r\n]+/);
+ let messages = om.message.split(/[\r\n]+/);
let maxLength = this.getMaxMessageLength();
// Attempt to smartly split a string into multiple lines (based on the
// maximum number of characters the message can contain).
for (let i = 0; i < messages.length; ++i) {
let message = messages[i];
let length = this._account.countBytes(message);
@@ -154,34 +154,34 @@ const GenericIRCConversation = {
// Remove the current message and insert the two new ones. If no space was
// found, cut the first message to the maximum length and start the second
// message one character after that. If a space was found, exclude it.
messages.splice(i, 1, message.substr(0, index == -1 ? maxLength : index),
message.substr((index + 1) || maxLength));
}
- // Send each message and display it in the conversation.
- for (let message of messages) {
- if (!message.length)
- return;
+ return messages;
+ },
+ sendMsg: function(aMessage) {
+ if (!aMessage.length)
+ return;
- if (!this._account.sendMessage("PRIVMSG", [this.name, message])) {
- this.writeMessage(this._account._currentServerName,
- _("error.sendMessageFailed"),
- {error: true, system: true});
- break;
- }
+ if (!this._account.sendMessage("PRIVMSG", [this.name, aMessage])) {
+ this.writeMessage(this._account._currentServerName,
+ _("error.sendMessageFailed"),
+ {error: true, system: true});
+ return;
+ }
- // Since the server doesn't send us a message back, just assume the
- // message was received and immediately show it.
- this.writeMessage(this._account._nickname, message, {outgoing: true});
+ // Since the server doesn't send us a message back, just assume the
+ // message was received and immediately show it.
+ this.writeMessage(this._account._nickname, aMessage, {outgoing: true});
- this._pendingMessage = true;
- }
+ this._pendingMessage = true;
},
// IRC doesn't support typing notifications, but it does have a maximum
// message length.
sendTyping: function(aString) {
let longestLineLength =
Math.max.apply(null, aString.split("\n").map(this._account.countBytes,
this._account));
return this.getMaxMessageLength() - longestLineLength;
diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -28,16 +28,21 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
"@mozilla.org/image/tools;1",
"imgITools");
XPCOMUtils.defineLazyGetter(this, "_", function()
l10nHelper("chrome://chat/locale/xmpp.properties")
);
+XPCOMUtils.defineLazyGetter(this, "TXTToHTML", function() {
+ let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(Ci.mozITXTToHTMLConv);
+ return function(aTXT) cs.scanTXT(aTXT, cs.kEntities);
+});
+
/* This is an ordered list, used to determine chat buddy flags:
* index < member -> noFlags
* index = member -> voiced
* moderator -> halfOp
* admin -> op
* owner -> founder
*/
const kRoles = ["outcast", "visitor", "participant", "member", "moderator",
@@ -233,32 +238,31 @@ const XMPPConversationPrototype = {
_targetResource: "",
get to() {
let to = this.buddy.userName;
if (this._targetResource)
to += "/" + this._targetResource;
return to;
},
+ prepareForSending: function(om) [TXTToHTML(om.message)],
+
/* Called when the user enters a chat message */
- sendMsg: function (aMsg) {
+ sendMsg: function(aMsg) {
this._cancelTypingTimer();
let cs = this.shouldSendTypingNotifications ? "active" : null;
let s = Stanza.message(this.to, aMsg, cs);
this._account.sendStanza(s);
let who;
if (this._account._connection)
who = this._account._connection._jid.jid;
if (!who)
who = this._account.name;
let alias = this.account.alias || this.account.statusInfo.displayName;
- let msg = Cc["@mozilla.org/txttohtmlconv;1"]
- .getService(Ci.mozITXTToHTMLConv)
- .scanTXT(aMsg, Ci.mozITXTToHTMLConv.kEntities);
- this.writeMessage(who, msg, {outgoing: true, _alias: alias});
+ this.writeMessage(who, aMsg, {outgoing: true, _alias: alias});
delete this._typingState;
},
/* Called by the account when a messsage is received from the buddy */
incomingMessage: function(aMsg, aStanza, aDate) {
let from = aStanza.attributes["from"];
this._targetResource = this._account._parseJID(from).resource;
let flags = {};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment