Bug 953944 - Implement IRC in JavaScript, r=florian.

This commit is contained in:
Patrick Cloke 2012-02-27 15:19:30 +01:00
Родитель add113cd19
Коммит e9b4146acb
30 изменённых файлов: 3835 добавлений и 23 удалений

Просмотреть файл

@ -44,6 +44,7 @@ include $(DEPTH)/config/autoconf.mk
PROTOCOLS = \ PROTOCOLS = \
facebook \ facebook \
gtalk \ gtalk \
irc \
twitter \ twitter \
xmpp \ xmpp \
$(NULL) $(NULL)

Просмотреть файл

@ -62,6 +62,9 @@ pref("messenger.status.defaultIdleAwayMessage", "chrome://chat/locale/status.pro
pref("messenger.status.userIconFileName", ""); pref("messenger.status.userIconFileName", "");
pref("messenger.status.userDisplayName", ""); pref("messenger.status.userDisplayName", "");
// Default message used when quitting IRC. This is overridable per account.
pref("chat.irc.defaultQuitMessage", "");
// loglevel is the minimum severity level that a libpurple message // loglevel is the minimum severity level that a libpurple message
// must have to be reported in the Error Console. // must have to be reported in the Error Console.
// //

Просмотреть файл

@ -102,7 +102,10 @@ ConversationLog.prototype = {
format: "txt", format: "txt",
_init: function cl_init() { _init: function cl_init() {
let file = getLogFolderForAccount(this._conv.account, true); let file = getLogFolderForAccount(this._conv.account, true);
file.append(this._conv.normalizedName); let name = this._conv.normalizedName;
if (this._conv.isChat && this._conv.account.protocol.id != "prpl-twitter")
name += ".chat";
file.append(name);
if (!file.exists()) if (!file.exists())
file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777); file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
if (Services.prefs.getCharPref("purple.logging.format") == "json") if (Services.prefs.getCharPref("purple.logging.format") == "json")
@ -443,8 +446,13 @@ Logger.prototype = {
}, },
getLogsForAccountBuddy: function logger_getLogsForAccountBuddy(aAccountBuddy) getLogsForAccountBuddy: function logger_getLogsForAccountBuddy(aAccountBuddy)
this._enumerateLogs(aAccountBuddy.account, aAccountBuddy.normalizedName), this._enumerateLogs(aAccountBuddy.account, aAccountBuddy.normalizedName),
getLogsForConversation: function logger_getLogsForConversation(aConversation) getLogsForConversation: function logger_getLogsForConversation(aConversation) {
this._enumerateLogs(aConversation.account, aConversation.normalizedName), let name = aConversation.normalizedName;
if (aConversation.isChat &&
aConversation.account.protocol.id != "prpl-twitter")
name += ".chat";
return this._enumerateLogs(aConversation.account, name);
},
getSystemLogsForAccount: function logger_getSystemLogsForAccount(aAccount) getSystemLogsForAccount: function logger_getSystemLogsForAccount(aAccount)
this._enumerateLogs(aAccount, ".system"), this._enumerateLogs(aAccount, ".system"),

Просмотреть файл

@ -0,0 +1,148 @@
# LOCALIZATION NOTE (connection.*)
# These will be displayed in the account manager in order to show the progress
# of the connection.
# (These will be displayed in account.connection.progress from
# accounts.properties, which adds … at the end, so do not include
# periods at the end of these messages.)
connection.quitting=Sending the QUIT message
# LOCALIZATION NOTE (connection.error.*)
# These will show in the account manager if an error occurs during the
# connection attempt.
connection.error.lost=Lost connection with server
connection.error.timeOut=Connection timed out
connection.error.certError=Certification error when connecting to server
# LOCALIZATION NOTE
# These show up on the join chat menu. An underscore is for the access key.
joinChat.channel=_Channel
joinChat.password=_Password
# LOCALIZATION NOTE
# These are the protocol specific options shown in the account manager and
# account wizard windows.
options.server=Server
options.port=Port
options.ssl=Use SSL
options.encoding=Character Set
options.quitMessage=Quit message
options.partMessage=Part message
options.showServerTab=Show messages from the server
# LOCALIZATION NOTE:
# %1$S is the nickname of the user who was pinged.
# %2$S is the delay (in seconds).
ctcp.ping=Ping reply from %1$S in %2$S seconds.
# %1$S is the nickname of the user whose version was requested.
# %2$S is the version response from the client.
ctcp.version=%1$S is using "%2$S"
# %1$S is the nickname of the user whose time was requested.
# %2$S is the time response.
ctcp.time=The time for %1$S is %2$S.
# LOCALZIATION NOTE (command.*):
# These are the help messages for each command, the %S is the command name
# Each command first gives the parameter it accepts and then a description of
# the command.
command.action=%S <action to perform>: Perform an action.
command.ctcp=%S <nick> <msg>: Sends a CTCP message to the nick.
command.chanserv=%S <command>: Send a command to ChanServ.
command.deop=%S <nick1>[,<nick2>]*: Remove channel operator status from someone. You must be a channel operator to do this.
command.devoice=%S <nick1>[,<nick2>]*: Remove channel voice status from someone, preventing them from speaking if the channel is moderated (+m). You must be a channel operator to do this.
command.invite=%S <nick> [<room>]: Invite someone to join you in the specified channel, or the current channel.
command.join=%S <room1>[,<room2>]* [<key1>[,<key2>]*]: Enter one or more channels, optionally providing a channel key for each if needed.
command.kick=%S <nick> [<message>]: Remove someone from a channel. You must be a channel operator to do this.
command.list=%S: Display a list of chat rooms on the network. Warning, some servers may disconnect you upon doing this.
command.memoserv=%S <command>: Send a command to MemoServ.
command.mode=%S (<nick>|<channel>) (+|-)<new mode>: Set or unset a channel or user mode.
command.msg=%S <nick> <message>: Send a private message to a user (as opposed to a channel).
command.nick=%S <new nickname>: Change your nickname.
command.nickserv=%S <command>: Send a command to NickServ.
command.notice=%S <target> <message>: Send a notice to a user or channel.
command.op=%S <nick1>[,<nick2>]*: Grant channel operator status to someone. You must be a channel operator to do this.
command.operserv=%S <command>: Send a command to OperServ.
command.part=%S [message]: Leave the current channel with an optional message.
command.ping=%S [<nick>]: Asks how much lag a user (or the server if no user specified) has.
command.quit=%S <message>: Disconnect from the server, with an optional message.
command.quote=%S <command>: Send a raw command to the server.
command.time=%S: Displays the current local time at the IRC server.
command.topic=%S [<new topic>]: View or change the channel topic.
command.umode=%S (+|-)<new mode>: Set or unset a user mode.
command.version=%S <nick>: Request the version of a user's client.
command.voice=%S <nick1>[,<nick2>]*: Grant channel voice status to someone. You must be a channel operator to do this.
command.wallops=%S <message>: If you don't know what this is, you probably can't use it (sends a command to all connected with the +w flag and all operators on the server.
command.whowas=%S <nick>: Get information on a user that has logged off.
# LOCALIZATION NOTE (message.*):
# These are shown as system messages in the conversation.
# %1$S is the nick and %2$S is the nick and host of the user who joined.
message.join=%1$S [%2$S] entered the room.
# %1$S is the nick of who kicked you.
# %2$S is message.kicked.reason, if a kick message was given.
message.kicked.you=You have been kicked by %1$S%2$S.
# %1$S is the nick that is kicked, %2$S the nick of the person who kicked
# %1$S. %3$S is message.kicked.reason, if a kick message was given.
message.kicked=%1$S has been kicked by %2$S%3$S.
# %S is the kick message
message.kicked.reason=: %S
# %1$S is the nickname of the user whose mode was changed, %2$S is the new
# mode and %3$S is who set the mode.
message.mode=mode (%1$S %2$S) by %3$S.
# %1$S is the old nick and %2$S is the new nick.
message.nick=%1$S is now known as %2$S.
# %S is your new nick.
message.nick.you=You are now known as %S.
# The paramter is the message.parted.reason, if a part message is given.
message.parted.you=You have left the room (Part%1$S).
# %1$S is the user's nick, %2$S is message.parted.reason, if a part message is given.
message.parted=%1$S has left the room (Part%2$S).
# %S is the part message supplied by the user.
message.parted.reason=: %S
# %1$S is the user's nick, %2$S is message.quit2 if a quit message is given.
message.quit=%1$S has left the room (Quit%2$S).
# The paramter is the quit message given by the user.
message.quit2=: %S
# %1$S is the user who changed the topic, %2$S is the new topic.
message.topicChanged=%1$S has changed the topic to: %2$S.
# %1$S is the user who cleared the topic.
message.topicCleared=%1$S has cleared the topic.
# %1$S is the conversation name, %2$S is the topic.
message.topic=The topic for %1$S is: %2$S.
# %S is the conversation name.
message.topicRemoved=The topic for %S was removed.
# %1$S is the nickname of the invited user, %2$S is the conversation name
# they were invited to.
message.invited=%1$S was successfully invited to %2$S.
# %S is the nickname of the user who was summoned.
message.summoned=%S was summoned.
# LOCALIZATION NOTE (error.*):
# These are shown as error messages in the conversation.
# %S is the channel name.
error.noChannel=There is no channel: %S.
error.tooManyChannels=Cannot join %S; you've joined too many channels.
# %1$S is your new nick, %2$S is the kill message from the server.
error.nickCollision=Nick already in use, changing nick to %1$S [%2$S].
error.banned=You are banned from this server.
error.bannedSoon=You will soon be banned from this server.
error.mode.wrongUser=You cannot change modes for other users.
# LOCALIZATION NOTE (tooltip.*):
# These are the descriptions given in a tooltip with information received
# from a whois response.
# The username is set by the user's IRC client, usually to the client's name
# but the user can change it.
tooltip.user=Username
# The host name that the user connects from (usually based on the
# reverse DNS of the user's IP, but often mangled by the server to
# protect users).
tooltip.host=Host name
# The real name is a description of the user (including spaces).
tooltip.realname=Real name
# The away message of the user
tooltip.away=Away
tooltip.ircOp=IRC Operator
tooltip.channels=Currently on
tooltip.server=Server
# %1$S is the server name, %2$S is the server location.
tooltip.serverValue=%1$S (%2$S)
tooltip.idleTime=Idle for

Просмотреть файл

@ -6,6 +6,7 @@
locale/@AB_CD@/chat/commands.properties (%commands.properties) locale/@AB_CD@/chat/commands.properties (%commands.properties)
locale/@AB_CD@/chat/conversations.properties (%conversations.properties) locale/@AB_CD@/chat/conversations.properties (%conversations.properties)
locale/@AB_CD@/chat/facebook.properties (%facebook.properties) locale/@AB_CD@/chat/facebook.properties (%facebook.properties)
locale/@AB_CD@/chat/irc.properties (%irc.properties)
locale/@AB_CD@/chat/status.properties (%status.properties) locale/@AB_CD@/chat/status.properties (%status.properties)
locale/@AB_CD@/chat/twitter.properties (%twitter.properties) locale/@AB_CD@/chat/twitter.properties (%twitter.properties)
locale/@AB_CD@/chat/xmpp.properties (%xmpp.properties) locale/@AB_CD@/chat/xmpp.properties (%xmpp.properties)

Просмотреть файл

@ -43,6 +43,7 @@ chat/locales/Makefile
chat/modules/Makefile chat/modules/Makefile
chat/protocols/facebook/Makefile chat/protocols/facebook/Makefile
chat/protocols/gtalk/Makefile chat/protocols/gtalk/Makefile
chat/protocols/irc/Makefile
chat/protocols/jsTest/Makefile chat/protocols/jsTest/Makefile
chat/protocols/twitter/Makefile chat/protocols/twitter/Makefile
chat/protocols/xmpp/Makefile chat/protocols/xmpp/Makefile

Просмотреть файл

@ -114,7 +114,8 @@ function setTimeout(aFunction, aDelay)
} }
function clearTimeout(aTimer) function clearTimeout(aTimer)
{ {
aTimer.cancel(); if (aTimer)
aTimer.cancel();
} }
function executeSoon(aFunction) function executeSoon(aFunction)

Просмотреть файл

@ -101,6 +101,7 @@ const ForwardAccountPrototype = {
const GenericAccountPrototype = { const GenericAccountPrototype = {
__proto__: ClassInfo("prplIAccount", "generic account object"), __proto__: ClassInfo("prplIAccount", "generic account object"),
get wrappedJSObject() this,
_init: function _init(aProtocol, aImAccount) { _init: function _init(aProtocol, aImAccount) {
this.protocol = aProtocol; this.protocol = aProtocol;
this.imAccount = aImAccount; this.imAccount = aImAccount;
@ -411,6 +412,7 @@ Message.prototype = GenericMessagePrototype;
const GenericConversationPrototype = { const GenericConversationPrototype = {
__proto__: ClassInfo("prplIConversation", "generic conversation object"), __proto__: ClassInfo("prplIConversation", "generic conversation object"),
flags: Ci.nsIClassInfo.DOM_OBJECT, flags: Ci.nsIClassInfo.DOM_OBJECT,
get wrappedJSObject() this,
_init: function(aAccount, aName) { _init: function(aAccount, aName) {
this._account = aAccount; this._account = aAccount;

Просмотреть файл

@ -132,10 +132,6 @@ const Socket = {
// Set this for the segment size of outgoing binary streams. // Set this for the segment size of outgoing binary streams.
outputSegmentSize: 0, outputSegmentSize: 0,
// Use this to specify a URI scheme to the hostname when resolving the proxy,
// this may be unnecessary for some protocols.
uriScheme: "http://",
// Flags used by nsIProxyService when resolving a proxy. // Flags used by nsIProxyService when resolving a proxy.
proxyFlags: Ci.nsIProtocolProxyService.RESOLVE_PREFER_SOCKS_PROXY, proxyFlags: Ci.nsIProtocolProxyService.RESOLVE_PREFER_SOCKS_PROXY,
@ -170,9 +166,10 @@ const Socket = {
// Add a URI scheme since, by default, some protocols (i.e. IRC) don't // Add a URI scheme since, by default, some protocols (i.e. IRC) don't
// have a URI scheme before the host. // have a URI scheme before the host.
let uri = Services.io.newURI(this.uriScheme + this.host, null, null); let uri = Services.io.newURI("http://" + this.host, null, null);
this._proxyCancel = proxyService.asyncResolve(uri, this.proxyFlags, this); this._proxyCancel = proxyService.asyncResolve(uri, this.proxyFlags, this);
} catch(e) { } catch(e) {
Cu.reportError(e);
// We had some error getting the proxy service, just don't use one. // We had some error getting the proxy service, just don't use one.
this._createTransport(null); this._createTransport(null);
} }
@ -229,9 +226,10 @@ const Socket = {
this.serverSocket.close(); this.serverSocket.close();
}, },
// Send data on the output stream. // Send data on the output stream. Provide aLoggedData to log something
sendData: function(/* string */ aData) { // different than what is actually sent.
this.log("Sending:\n" + aData + "\n"); sendData: function(/* string */ aData, aLoggedData) {
this.log("Sending:\n" + (aLoggedData || aData));
try { try {
this._outputStream.write(aData + this.delimiter, this._outputStream.write(aData + this.delimiter,
@ -241,13 +239,15 @@ const Socket = {
} }
}, },
sendString: function(aString, aEncoding) { // Send a string to the output stream after converting the encoding. Provide
this.log("Sending:\n" + aString + "\n"); // aLoggedData to log something different than what is actually sent.
sendString: function(aString, aEncoding, aLoggedData) {
this.log("Sending:\n" + (aLoggedData || aString));
let converter = new ScriptableUnicodeConverter(); let converter = new ScriptableUnicodeConverter();
converter.charset = aEncoding || "UTF-8"; converter.charset = aEncoding || "UTF-8";
try { try {
let stream = converter.convertToInputStream(aString); let stream = converter.convertToInputStream(aString + this.delimiter);
this._outputStream.writeFrom(stream, stream.available()); this._outputStream.writeFrom(stream, stream.available());
} catch(e) { } catch(e) {
Cu.reportError(e); Cu.reportError(e);
@ -290,6 +290,10 @@ const Socket = {
* nsIProtocolProxyCallback methods * nsIProtocolProxyCallback methods
*/ */
onProxyAvailable: function(aRequest, aURI, aProxyInfo, aStatus) { onProxyAvailable: function(aRequest, aURI, aProxyInfo, aStatus) {
if (aProxyInfo) {
this.log("using " + aProxyInfo.type + " proxy: " +
aProxyInfo.host + ":" + aProxyInfo.port);
}
this._createTransport(aProxyInfo); this._createTransport(aProxyInfo);
delete this._proxyCancel; delete this._proxyCancel;
}, },

Просмотреть файл

@ -0,0 +1,64 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (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.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Instantbird.
#
# The Initial Developer of the Original Code is
# Patrick Cloke <clokep@gmail.com>.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
EXTRA_COMPONENTS = \
irc.js \
irc.manifest \
$(NULL)
EXTRA_JS_MODULES = \
ircBase.jsm \
ircCTCP.jsm \
ircDCC.jsm \
ircCommands.jsm \
ircHandlers.jsm \
ircISUPPORT.jsm \
ircUtils.jsm \
$(NULL)
ifdef ENABLE_TESTS
relativesrcdir = chat/protocols/irc
XPCSHELL_TESTS = test
endif
include $(topsrcdir)/config/rules.mk

Двоичные данные
chat/protocols/irc/icons/prpl-irc-32.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 695 B

Двоичные данные
chat/protocols/irc/icons/prpl-irc-48.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1003 B

Двоичные данные
chat/protocols/irc/icons/prpl-irc.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 454 B

917
chat/protocols/irc/irc.js Normal file
Просмотреть файл

@ -0,0 +1,917 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource:///modules/imXPCOMUtils.jsm");
Cu.import("resource:///modules/imServices.jsm");
Cu.import("resource:///modules/ircUtils.jsm");
Cu.import("resource:///modules/ircHandlers.jsm");
Cu.import("resource:///modules/jsProtoHelper.jsm");
Cu.import("resource:///modules/socket.jsm");
// Parses a raw IRC message into an object (see section 2.3 of RFC 2812).
function ircMessage(aData) {
LOG(aData);
let message = {rawMessage: aData};
let temp;
// Splits the raw string into four parts (the second is required), the command
// is required. A raw string looks like:
// [":" <source> " "] <command> [" " <parameter>]* [":" <last parameter>]
// <source>: :(<server url> | <nickname> [["!" <user>] "@" <host>])
// <command>: /[^ ]+/
// <parameter>: /[^ ]+/
// <last parameter>: /.+/
// See http://joshualuckers.nl/2010/01/10/regular-expression-to-match-raw-irc-messages/
if ((temp = aData.match(/^(?::([^ ]+) )?([^ ]+)(?: ((?:[^: ][^ ]* ?)*))?(?: ?:(.*))?$/))) {
// Assume message is from the server if not specified
message.source = temp[1] || this._server;
message.command = temp[2];
// Space separated parameters
message.params = temp[3] ? temp[3].trim().split(/ +/) : [];
if (temp[4]) // Last parameter can contain spaces
message.params.push(temp[4]);
// The source string can be split into multiple parts as:
// :(server|nickname[[!user]@host]
// If the source contains a . or a :, assume it's a server name. See RFC
// 2812 Section 2.3 definition of servername vs. nickname.
if (message.source &&
(temp = message.source.match(/^([^ !@\.:]+)(?:!([^ @]+))?(?:@([^ ]+))?$/))) {
message.nickname = temp[1];
message.user = temp[2] || null; // Optional
message.host = temp[3] || null; // Optional
}
}
return message;
}
function ircChannel(aAccount, aName, aNick) {
this._init(aAccount, aName, aNick);
}
ircChannel.prototype = {
__proto__: GenericConvChatPrototype,
sendMsg: function(aMessage) {
this._account.sendMessage("PRIVMSG", [this.name, aMessage]);
// Since we don't receive a message back from the server, just assume it
// was received and write it. An IRC bouncer will send us our message back
// though, try to handle that.
if (this.hasParticipant(this._account._nickname))
this.writeMessage(this.nick, aMessage, {outgoing: true});
},
// Overwrite the writeMessage function to apply CTCP formatting before
// display.
writeMessage: function(aWho, aText, aProperties) {
GenericConvChatPrototype.writeMessage.call(this, aWho,
ctcpFormatToHTML(aText),
aProperties);
},
// Section 3.2.2 of RFC 2812.
part: function(aMessage) {
let params = [this.name];
// If a valid message was given, use it as the part message.
// Otherwise, fall back to the default part message, if it exists.
let msg = aMessage || this._account.getString("partmsg");
if (msg)
params.push(msg);
this._account.sendMessage("PART", params);
},
unInit: function() {
// Tell the server about the part if connected.
if (this._account.connected)
this.part();
// Always remove the conversation.
this._account.removeConversation(this.name);
GenericConvChatPrototype.unInit.call(this);
},
getNormalizedChatBuddyName: function(aNick)
this._account.normalize(aNick, this._account.userPrefixes),
hasParticipant: function(aNick)
hasOwnProperty(this._participants, this.getNormalizedChatBuddyName(aNick)),
getParticipant: function(aNick, aNotifyObservers) {
let normalizedNick = this.getNormalizedChatBuddyName(aNick);
if (this.hasParticipant(aNick))
return this._participants[normalizedNick];
let participant = new ircParticipant(aNick, this._account);
this._participants[normalizedNick] = participant;
if (aNotifyObservers) {
this.notifyObservers(new nsSimpleEnumerator([participant]),
"chat-buddy-add");
}
return participant;
},
updateNick: function(aOldNick, aNewNick) {
if (!this.hasParticipant(aOldNick)) {
ERROR("Trying to rename nick that doesn't exist! " + aOldNick + " to " +
aNewNick);
return;
}
// Get the original ircParticipant and then remove it.
let participant = this.getParticipant(aOldNick);
this.removeParticipant(aOldNick);
// Update the nickname and add it under the new nick.
participant._name = aNewNick;
this._participants[this.getNormalizedChatBuddyName(aNewNick)] = participant;
this.notifyObservers(participant, "chat-buddy-update", aOldNick);
},
removeParticipant: function(aNick, aNotifyObservers) {
if (!this.hasParticipant(aNick))
return;
if (aNotifyObservers) {
let stringNickname = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
stringNickname.data = aNick;
this.notifyObservers(new nsSimpleEnumerator([stringNickname]),
"chat-buddy-remove");
}
delete this._participants[this.getNormalizedChatBuddyName(aNick)];
},
// Use this before joining to avoid errors of trying to re-add an existing
// participant
removeAllParticipants: function() {
let stringNicknames = [];
for (let nickname in this._participants) {
let stringNickname = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
stringNickname.data = this._participants[nickname].name;
stringNicknames.push(stringNickname);
}
this.notifyObservers(new nsSimpleEnumerator(stringNicknames),
"chat-buddy-remove");
this._participants = {};
},
_left: false,
get left() this._left,
set left(aLeft) {
this._left = aLeft;
// If we've left, notify observers.
if (this._left)
this.notifyObservers(null, "update-conv-chatleft");
},
get topic() this._topic, // can't add a setter without redefining the getter
set topic(aTopic) {
this._account.sendMessage("TOPIC", [this.name, aTopic]);
},
get topicSettable() true,
get normalizedName() this._account.normalize(this.name),
};
function ircParticipant(aName, aAccount) {
this._name = aName;
this._account = aAccount;
this._modes = [];
if (this._name[0] in this._account.userPrefixToModeMap) {
this._modes.push(this._account.userPrefixToModeMap[this._name[0]]);
this._name = this._name.slice(1);
}
}
ircParticipant.prototype = {
__proto__: GenericConvChatBuddyPrototype,
// This takes a mode change string and reflects it into the
// prplIConvChatBuddy, a mode change string is of the form:
// ("+" | "-")<mode key>[<mode key>]*
// e.g. +iaw or -i
setMode: function(aNewMode) {
// Are we going to add or remove the modes?
if (aNewMode[0] != "+" && aNewMode[0] != "-") {
WARN("Invalid mode string: " + aNewMode);
return;
}
let addNewMode = aNewMode[0] == "+";
// Check each mode being added and update the user
for (let i = 1; i < aNewMode.length; i++) {
let index = this._modes.indexOf(aNewMode[i]);
// If the mode is in the list of modes and we want to remove it.
if (index != -1 && !addNewMode)
this._modes.splice(index, 1);
// If the mode is not in the list of modes and we want to add it.
else if (index == -1 && addNewMode)
this._modes.push(aNewMode[i]);
}
},
get voiced() this._modes.indexOf("v") != -1,
get halfOp() this._modes.indexOf("h") != -1,
get op() this._modes.indexOf("o") != -1,
get founder() this._modes.indexOf("n") != -1,
get typing() false
};
function ircConversation(aAccount, aName) {
this._init(aAccount, aName);
}
ircConversation.prototype = {
__proto__: GenericConvIMPrototype,
sendMsg: function(aMessage) {
this._account.sendMessage("PRIVMSG", [this.name, aMessage]);
// 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});
},
// Overwrite the writeMessage function to apply CTCP formatting before
// display.
writeMessage: function(aWho, aText, aProperties) {
GenericConvIMPrototype.writeMessage.call(this, aWho,
ctcpFormatToHTML(aText),
aProperties);
},
get normalizedName() this._account.normalize(this.name),
unInit: function() {
this._account.removeConversation(this.name);
GenericConvIMPrototype.unInit.call(this);
},
updateNick: function(aNewNick) {
this._name = aNewNick;
this.notifyObservers(null, "update-conv-title");
}
};
function ircSocket(aAccount) {
this._account = aAccount;
this._initCharsetConverter();
}
ircSocket.prototype = {
__proto__: Socket,
delimiter: "\r\n",
connectTimeout: 60, // Failure to connect after 1 minute
readWriteTimeout: 300, // Failure when no data for 5 minutes
_converter: null,
_initCharsetConverter: function() {
this._converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
try {
this._converter.charset = this._account._encoding;
} catch (e) {
delete this._converter;
ERROR("Failed to set character set to: " + this._account._encoding + " for " +
this._account.name + ".");
}
},
// Implement Section 5 of RFC 2812.
onDataReceived: function(aRawMessage) {
DEBUG(aRawMessage);
if (this._converter) {
try {
aRawMessage = this._converter.ConvertToUnicode(aRawMessage);
} catch (e) {
WARN("This message doesn't seem to be " + this._account._encoding +
" encoded: " + aRawMessage);
// Unfortunately, if the unicode converter failed once,
// it will keep failing so we need to reinitialize it.
this._initCharsetConverter();
}
}
// If nothing handled the message, throw an error.
if (!ircHandlers.handleMessage(this._account, new ircMessage(aRawMessage)))
WARN("Unhandled IRC message: " + aRawMessage);
},
onConnection: function() {
this._account._connectionRegistration.call(this._account);
},
// Throw errors if the socket has issues.
onConnectionClosed: function () {
if (!this._account.imAccount || this._account.disconnecting ||
this._account.disconnected)
return;
ERROR("Connection closed by server.");
this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
_("connection.error.lost"));
},
onConnectionReset: function () {
ERROR("Connection reset.");
this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
_("connection.error.lost"));
},
onConnectionTimedOut: function() {
ERROR("Connection timed out.");
this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
_("connection.error.timeOut"));
},
onCertificationError: function(aSocketInfo, aStatus, aTargetSite) {
ERROR("Certification error.");
this._account.gotDisconnected(Ci.prplIAccount.ERROR_CERT_OTHER_ERROR,
_("connection.error.certError"));
},
log: LOG
};
function ircAccountBuddy(aAccount, aBuddy, aTag, aUserName) {
this._init(aAccount, aBuddy, aTag, aUserName);
}
ircAccountBuddy.prototype = {
__proto__: GenericAccountBuddyPrototype,
// Returns a list of imITooltipInfo objects to be displayed when the user
// hovers over the buddy.
getTooltipInfo: function() this._account.getBuddyInfo(this.normalizedName),
get normalizedName() this._account.normalize(this.userName),
// Can not send messages to buddies who appear offline.
get canSendMessage() this.account.connected,
// Called when the user wants to chat with the buddy.
createConversation: function() {
ERROR(this.userName);
return this._account.createConversation(this.userName);
}
};
function ircAccount(aProtocol, aImAccount) {
this._buddies = {};
this._init(aProtocol, aImAccount);
this._conversations = {};
// Split the account name into usable parts.
let splitter = aImAccount.name.lastIndexOf("@");
this._nickname = aImAccount.name.slice(0, splitter);
this._server = aImAccount.name.slice(splitter + 1);
// For more information, see where these are defined in the prototype below.
this._isOnQueue = [];
this.pendingIsOnQueue = [];
this.whoisInformation = {};
}
ircAccount.prototype = {
__proto__: GenericAccountPrototype,
_socket: null,
_MODE_WALLOPS: 1 << 2, // mode 'w'
_MODE_INVISIBLE: 1 << 3, // mode 'i'
get _mode() 0,
get noNewlines() true,
get normalizedName() this.normalize(this.name),
// Parts of the specification give max lengths, keep track of them since a
// server can overwrite them. The defaults given here are from RFC 2812.
maxNicknameLength: 9, // 1.2.1 Users
maxChannelLength: 50, // 1.3 Channels
maxMessageLength: 512, // 2.3 Messages
maxHostnameLength: 63, // 2.3.1 Message format in Augmented BNF
// The default prefixes.
userPrefixes: ["@", "!", "%", "+"],
// The default prefixes to modes.
userPrefixToModeMap: {"@": "o", "!": "n", "%": "h", "+": "v"},
channelPrefixes: ["&", "#", "+", "!"], // 1.3 Channels
// Handle Scandanavian lower case (optionally remove status indicators).
// See Section 2.2 of RFC 2812: the characters {}|^ are considered to be the
// lower case equivalents of the characters []\~, respectively.
normalize: function(aStr, aPrefixes) {
let str = aStr;
if (aPrefixes && aPrefixes.indexOf(aStr[0]) != -1)
str = str.slice(1);
return str.replace(/[\x41-\x5E]/g,
function(c) String.fromCharCode(c.charCodeAt(0) + 0x20));
},
isMUCName: function(aStr) {
return (this.channelPrefixes.indexOf(aStr[0]) != -1);
},
// When Instantbird changes status, tell the server. IRC is only away or not
// away; consider away, idle and unavailable to be away. This will also
// connect or disconnect if set to offline/available.
isAway: false,
observe: function(aSubject, aTopic, aData) {
if (aTopic != "status-changed")
return;
this.updateStatus();
},
// Update the accounts status when Instantbird requests a status change and
// when we receive information from the server that the status has changed.
updateStatus: function() {
let {statusType: type, statusText: text} = this.imAccount.statusInfo;
LOG("New status received: " + type + "\r\n" + text);
// Tell the server to mark us as away.
if (type < Ci.imIStatusInfo.STATUS_AVAILABLE && !this.isAway) {
// We have to have a string in order to set IRC as AWAY.
if (!text) {
// If no status is given, use the the default idle/away message.
const IDLE_PREF_BRANCH = "messenger.status.";
const IDLE_PREF = "defaultIdleAwayMessage";
text = Services.prefs.getComplexValue(IDLE_PREF_BRANCH + IDLE_PREF,
Ci.nsIPrefLocalizedString).data;
if (!text) {
// Get the default value of the localized preference.
text = Services.prefs.getDefaultBranch(IDLE_PREF_BRANCH)
.getComplexValue(IDLE_PREF,
Ci.nsIPrefLocalizedString).data;
}
// The last resort, fallback to a non-localized string.
if (!text)
text = "Away";
}
this.sendMessage("AWAY", text); // Mark as away.
}
else if (type == Ci.imIStatusInfo.STATUS_AVAILABLE && this.isAway)
this.sendMessage("AWAY"); // Mark as back.
},
// The whois information: nicks are used as keys and refer to a map of field
// to value.
whoisInformation: {},
// Request WHOIS information on a buddy when the user requests more
// information.
requestBuddyInfo: function(aBuddyName) {
this.sendMessage("WHOIS", aBuddyName);
},
// Return an nsISimpleEnumerator of imITooltipInfo for a given nick.
getBuddyInfo: function(aNick) {
let nick = this.normalize(aNick);
if (!hasOwnProperty(this.whoisInformation, nick))
return EmptyEnumerator;
let whoisInformation = this.whoisInformation[nick];
let tooltipInfo = [];
for (let field in whoisInformation) {
let value = whoisInformation[field];
tooltipInfo.push(new TooltipInfo(_("tooltip." + field), value));
}
return new nsSimpleEnumerator(tooltipInfo);
},
addBuddy: function(aTag, aName) {
let buddy = new ircAccountBuddy(this, null, aTag, aName);
this._buddies[buddy.normalizedName] = buddy;
// Put the username as the first to be checked on the next ISON call.
this._isOnQueue.unshift(buddy.userName);
Services.contacts.accountBuddyAdded(buddy);
},
// Loads a buddy from the local storage. Called for each buddy locally stored
// before connecting to the server.
loadBuddy: function(aBuddy, aTag) {
let buddy = new ircAccountBuddy(this, aBuddy, aTag);
this._buddies[buddy.normalizedName] = buddy;
// Put each buddy name into the ISON queue.
this._isOnQueue.push(buddy.userName);
return buddy;
},
hasBuddy: function(aName)
hasOwnProperty(this._buddies, this.normalize(aName, this.userPrefixes)),
// Return an array of buddy names.
getBuddyNames: function() {
let buddies = [];
for each (let buddyName in Object.keys(this._buddies))
buddies.push(this._buddies[buddyName].userName);
return buddies;
},
getBuddy: function(aName) {
if (this.hasBuddy(aName))
return this._buddies[this.normalize(aName, this.userPrefixes)];
return null;
},
changeBuddyNick: function(aOldNick, aNewNick) {
let msg;
// Your nickname changed!
if (this.normalize(aOldNick) == this.normalize(this._nickname)) {
this._nickname = aNewNick;
msg = _("message.nick.you", aNewNick);
}
else
msg = _("message.nick", aOldNick, aNewNick);
for each (let conversation in this._conversations) {
if (conversation.isChat && conversation.hasParticipant(aOldNick)) {
// Update the nick in every chat conversation the user is in.
conversation.updateNick(aOldNick, aNewNick);
conversation.writeMessage(aOldNick, msg, {system: true});
}
}
// If a private conversation is open with that user, change its title.
if (this.hasConversation(aOldNick)) {
// Get the current conversation and rename it.
let conversation = this.getConversation(aOldNick);
// Remove the old reference to the conversation and create a new one.
this.removeConversation(aOldNick);
this._conversations[this.normalize(aNewNick)] = conversation;
conversation.updateNick(aNewNick);
conversation.writeMessage(aOldNick, msg, {system: true});
}
},
countBytes: function(aStr) {
// Assume that if it's not UTF-8 then each character is 1 byte.
if (this._encoding != "UTF-8")
return aStr.length;
// Count the number of bytes in a UTF-8 encoded string.
function charCodeToByteCount(c) {
// Unicode characters with a code point > 127 are 2 bytes long.
// Unicode characters with a code point > 2047 are 3 bytes long.
return c < 128 ? 1 : c < 2048 ? 2 : 3;
}
let bytes = 0;
for (let i = 0; i < aStr.length; i++)
bytes += charCodeToByteCount(aStr.charCodeAt(i));
return bytes;
},
// To check if users are online, we need to queue multiple messages.
// An internal queue of all nicks that we wish to know the status of.
_isOnQueue: [],
// The nicks that were last sent to the server that we're waiting for a
// response about.
pendingIsOnQueue: [],
// The time between sending isOn messages (milliseconds).
_isOnDelay: 60 * 1000,
_isOnTimer: null,
// The number of characters that are available to be filled with nicks for
// each ISON message.
_isOnLength: null,
// Generate and send an ISON message to poll for each nick's status.
sendIsOn: function() {
// If no buddies, just look again after the timeout.
if (this._isOnQueue.length) {
// Calculate the possible length of names we can send.
if (!this._isOnLength) {
let length = this.countBytes(this.buildMessage("ISON", " ")) + 2;
this._isOnLength = this.maxMessageLength - length + 1;
}
// Add any previously pending queue to the end of the ISON queue.
if (this.pendingIsOnQueue)
this._isOnQueue = this._isOnQueue.concat(this.pendingIsOnQueue);
// Always add the next nickname to the pending queue, this handles a silly
// case where the next nick is greater than or equal to the maximum
// message length.
this.pendingIsOnQueue = [this._isOnQueue.shift()];
// Attempt to maximize the characters used in each message, this may mean
// that a specific user gets sent very often since they have a short name!
let buddiesLength = this.countBytes(this.pendingIsOnQueue[0]);
for (let i = 0; i < this._isOnQueue.length; i++) {
// If we can fit the nick, add it to the current buffer.
if ((buddiesLength + this.countBytes(this._isOnQueue[i])) < this._isOnLength) {
// Remove the name from the list and add it to the pending queue.
let nick = this._isOnQueue.splice(i--, 1)[0];
this.pendingIsOnQueue.push(nick);
// Keep track of the length of the string, the + 1 is for the spaces.
buddiesLength += this.countBytes(nick) + 1;
// If we've filled up the message, stop looking for more nicks.
if (buddiesLength >= this._isOnLength)
break;
}
}
// Send the message.
this.sendMessage("ISON", this.pendingIsOnQueue.join(" "));
}
// Call this function again in _isOnDelay seconds.
// This makes the assumption that this._isOnDelay >> the response to ISON
// from the server.
this._isOnTimer = setTimeout(this.sendIsOn.bind(this), this._isOnDelay);
},
connect: function() {
this.reportConnecting();
// Load preferences.
this._port = this.getInt("port");
this._ssl = this.getBool("ssl");
// Use the display name as the user's real name.
this._realname = this.imAccount.statusInfo.displayName;
this._encoding = this.getString("encoding") || "UTF-8";
this._showServerTab = this.getBool("showServerTab");
// Open the socket connection.
this._socket = new ircSocket(this);
this._socket.connect(this._server, this._port, this._ssl ? ["ssl"] : []);
},
// Used to wait for a response from the server.
_quitTimer: null,
// RFC 2812 Section 3.1.7.
quit: function(aMessage) {
this.sendMessage("QUIT",
aMessage || this.getString("quitmsg") || undefined);
},
// When the user clicks "Disconnect" in account manager
disconnect: function() {
if (this.disconnected || this.disconnecting)
return;
this.reportDisconnecting(Ci.prplIAccount.NO_ERROR,
_("connection.quitting"));
// Let the server know we're going to disconnect.
this.quit();
// Give the server 2 seconds to respond, otherwise just forcefully
// disconnect the socket. This will be cancelled if a response is heard from
// the server.
this._quitTimer = setTimeout(this.gotDisconnected.bind(this), 2 * 1000);
},
createConversation: function(aName) this.getConversation(aName),
// aComponents implements prplIChatRoomFieldValues.
joinChat: function(aComponents) {
let params = [aComponents.getValue("channel")];
let password = aComponents.getValue("password");
if (password)
params.push(password);
this.sendMessage("JOIN", params);
},
chatRoomFields: {
"channel": {"label": _("joinChat.channel"), "required": true},
"password": {"label": _("joinChat.password"), "isPassword": true}
},
parseDefaultChatName: function(aDefaultName) ({"channel": aDefaultName}),
// Attributes
get canJoinChat() true,
hasConversation: function(aConversationName)
hasOwnProperty(this._conversations, this.normalize(aConversationName)),
// Returns a conversation (creates it if it doesn't exist)
getConversation: function(aName) {
let name = this.normalize(aName);
if (!this.hasConversation(aName)) {
let constructor = this.isMUCName(aName) ? ircChannel : ircConversation;
this._conversations[name] = new constructor(this, aName, this._nickname);
}
return this._conversations[name];
},
removeConversation: function(aConversationName) {
if (this.hasConversation(aConversationName))
delete this._conversations[this.normalize(aConversationName)];
},
// This builds the message string that will be sent to the server.
buildMessage: function(aCommand, aParams) {
if (!aCommand) {
ERROR("IRC messages must have a command.");
return null;
}
// Ensure a command is only characters or numbers.
if (!/^[A-Z0-9]+$/i.test(aCommand)) {
ERROR("IRC command invalid: " + aCommand);
return null;
}
let message = aCommand;
// If aParams is empty, then use an empty array. If aParams is not an array,
// consider it to be a single parameter and put it into an array.
let params = !aParams ? [] : Array.isArray(aParams) ? aParams : [aParams];
if (params.length) {
if (params.slice(0, -1).some(function(p) p.indexOf(" ") != -1)) {
ERROR("IRC parameters cannot have spaces: " + params.slice(0, -1));
return null;
}
// Join the parameters with spaces, except the last parameter which gets
// joined with a " :" before it (and can contain spaces).
message += " " + params.concat(":" + params.pop()).join(" ");
}
return message;
},
// Shortcut method to build & send a message at once. Use aLoggedData to log
// something different than what is actually sent.
sendMessage: function(aCommand, aParams, aLoggedData) {
this.sendRawMessage(this.buildMessage(aCommand, aParams), aLoggedData);
},
// This sends a message over the socket and catches any errors. Use
// aLoggedData to log something different than what is actually sent.
sendRawMessage: function(aMessage, aLoggedData) {
// XXX This should escape any characters that can't be used in IRC (e.g.
// \001, \r\n).
let length = this.countBytes(aMessage) + 2;
if (length > this.maxMessageLength) {
// Log if the message is too long, but try to send it anyway.
WARN("Message length too long (" + length + " > " +
this.maxMessageLength + "\n" + aMessage);
}
try {
this._socket.sendString(aMessage, this._encoding, aLoggedData);
} catch (e) {
try {
WARN("Failed to convert " + aMessage + " from Unicode to " +
this._encoding + ".");
this._socket.sendData(aMessage, aLoggedData);
} catch(e) {
ERROR("Socket error: " + e);
this.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
_("connection.error.lost"));
}
}
},
// CTCP messages are \001<COMMAND> [<parameters>]*\001.
sendCTCPMessage: function(aCommand, aParams, aTarget, aIsNotice) {
// Combine the CTCP command and parameters into the single IRC param.
let ircParam = "\x01" + aCommand;
// If aParams is empty, then use an empty array. If aParams is not an array,
// consider it to be a single parameter and put it into an array.
let params = !aParams ? [] : Array.isArray(aParams) ? aParams : [aParams];
if (params.length)
ircParam += " " + params.join(" ");
ircParam += "\x01";
// Send the IRC message as a NOTICE or PRIVMSG.
this.sendMessage(aIsNotice ? "NOTICE" : "PRIVMSG", [aTarget, ircParam]);
},
// Implement section 3.1 of RFC 2812
_connectionRegistration: function() {
// Send the password message, if provided (section 3.1.1).
if (this.imAccount.password) {
this.sendMessage("PASS", this.imAccount.password,
"PASS <password not logged>");
}
// Send the nick message (section 3.1.2).
this.sendMessage("NICK", this._nickname);
// Send the user message (section 3.1.3).
// Use brandShortName as the username.
let username =
l10nHelper("chrome://branding/locale/brand.properties")("brandShortName");
this.sendMessage("USER", [username, this._mode.toString(), "*",
this._realname || this._nickname]);
},
gotDisconnected: function(aError, aErrorMessage) {
if (!this.imAccount || this.disconnected)
return;
if (aError === undefined)
aError = Ci.prplIAccount.NO_ERROR;
// If we are already disconnecting, this call to gotDisconnected
// is when the server acknowledges our disconnection.
// Otherwise it's because we lost the connection.
if (!this.disconnecting)
this.reportDisconnecting(aError, aErrorMessage);
this._socket.disconnect();
delete this._socket;
clearTimeout(this._isOnTimer);
delete this._isOnTimer;
// Clean up each conversation: mark as left and remove participant.
for (let conversation in this._conversations) {
if (this.isMUCName(conversation)) {
// Remove the user's nick and mark the conversation as left as that's
// the final known state of the room.
this._conversations[conversation].removeParticipant(this._nickname, true);
this._conversations[conversation].left = true;
}
}
this.reportDisconnected();
},
unInit: function() {
delete this.imAccount;
// Disconnect if we're online while this gets called.
if (this._socket)
this._socket.disconnect();
clearTimeout(this._isOnTimer);
clearTimeout(this._quitTimer);
}
};
function ircProtocol() {
// ircCommands.jsm exports one variable: commands. Import this directly into
// the protocol object.
Cu.import("resource:///modules/ircCommands.jsm", this);
this.registerCommands();
// Register the standard handlers
let tempScope = {};
Cu.import("resource:///modules/ircBase.jsm", tempScope);
Cu.import("resource:///modules/ircISUPPORT.jsm", tempScope);
Cu.import("resource:///modules/ircCTCP.jsm", tempScope);
Cu.import("resource:///modules/ircDCC.jsm", tempScope);
// Register default IRC handlers (IRC base, CTCP).
ircHandlers.registerHandler(tempScope.ircBase);
ircHandlers.registerHandler(tempScope.ircISUPPORT);
ircHandlers.registerHandler(tempScope.ircCTCP);
// Register default CTCP handlers (CTCP base, DCC).
ircHandlers.registerCTCPHandler(tempScope.ctcpBase);
ircHandlers.registerCTCPHandler(tempScope.ctcpDCC);
}
ircProtocol.prototype = {
__proto__: GenericProtocolPrototype,
get name() "IRC",
get iconBaseURI() "chrome://prpl-irc/skin/",
get baseId() "prpl-irc",
usernameSplits: [
{label: _("options.server"), separator: "@",
defaultValue: "chat.freenode.net", reverse: true}
],
options: {
// Default to IRC over SSL.
"port": {label: _("options.port"), default: 6667},
"ssl": {label: _("options.ssl"), default: false},
// XXX We should attempt to auto-detect encoding instead.
"encoding": {label: _("options.encoding"), default: "UTF-8"},
"quitmsg": {label: _("options.quitMessage"),
get default() Services.prefs.getCharPref("chat.irc.defaultQuitMessage")},
"partmsg": {label: _("options.partMessage"), default: ""},
"showServerTab": {label: _("options.showServerTab"), default: false}
},
get chatHasTopic() true,
get slashCommandsNative() true,
// Passwords in IRC are optional, and are needed for certain functionality.
get passwordOptional() true,
getAccount: function(aImAccount) new ircAccount(this, aImAccount),
classID: Components.ID("{607b2c0b-9504-483f-ad62-41de09238aec}")
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([ircProtocol]);

Просмотреть файл

@ -0,0 +1,3 @@
component {607b2c0b-9504-483f-ad62-41de09238aec} irc.js
contract @mozilla.org/chat/irc;1 {607b2c0b-9504-483f-ad62-41de09238aec}
category im-protocol-plugin prpl-irc @mozilla.org/chat/irc;1

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,278 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* This implements the Client-to-Client Protocol (CTCP), a subprotocol of IRC.
* REVISED AND UPDATED CTCP SPECIFICATION
* http://www.alien.net.au/irc/ctcp.txt
*/
const EXPORTED_SYMBOLS = ["ircCTCP", "ctcpBase"];
const Cu = Components.utils;
Cu.import("resource:///modules/imXPCOMUtils.jsm");
Cu.import("resource:///modules/ircHandlers.jsm");
Cu.import("resource:///modules/ircUtils.jsm");
function lowLevelDequote(aString) {
// Dequote (low level) / Low Level Quoting
// Replace quote char \020 followed by 0, n, r or \020 with a null, line
// break, carriage return or \020, respectively. Any other character after
// \020 is replaced with itself.
const replacements = {"0": "\0", "n": "\n", "r": "\r"};
return aString.replace(/\x10./g, function(aStr) {
return replacements[aStr[1]] || aStr[1];
});
}
function highLevelDequote(aString) {
// Dequote (high level) / CTCP Level Quoting
// Replace quote char \134 followed by a or \134 with \001 or \134,
// respectively. Any other character after \134 is replaced with itself.
return aString.replace(/\x5C./g, function(aStr) {
return (aStr[1] == "a") ? "\x01" : aStr[1];
});
}
// Split into a CTCP message which is a single command and a single parameter:
// <command> " " <parameter>
// The high level dequote is to unescape \001 in the message content.
function CTCPMessage(aMessage, aRawCTCPMessage) {
let message = aMessage;
message.ctcp = {};
message.ctcp.rawMessage = aRawCTCPMessage;
let dequotedCTCPMessage = highLevelDequote(message.ctcp.rawMessage);
let separator = dequotedCTCPMessage.indexOf(" ");
// If there's no space, then only a command is given.
// Do not capitalize the command, case sensitive
if (separator == -1) {
message.ctcp.command = dequotedCTCPMessage;
message.ctcp.param = "";
}
else {
message.ctcp.command = dequotedCTCPMessage.slice(0, separator);
message.ctcp.param = dequotedCTCPMessage.slice(separator + 1);
}
return message;
}
// This is the CTCP handler for IRC protocol, it will call each CTCP handler.
var ircCTCP = {
name: "CTCP",
// Slightly above default RFC 2812 priority.
priority: ircHandlers.HIGH_PRIORITY,
// CTCP uses only PRIVMSG and NOTICE commands.
commands: {
"PRIVMSG": ctcpHandleMessage,
"NOTICE": ctcpHandleMessage
}
}
// Parse the message and call all CTCP handlers on the message.
function ctcpHandleMessage(aMessage) {
// If there are no CTCP handlers, then don't parse the CTCP message.
if (!ircHandlers.hasCTCPHandlers)
return false;
// The raw CTCP message is in the last parameter of the IRC message.
let ctcpRawMessage = lowLevelDequote(aMessage.params.slice(-1)[0]);
// Split the raw message into the multiple CTCP messages and pull out the
// command and parameters.
let ctcpMessages = [];
let otherMessage = ctcpRawMessage.replace(/\x01([^\x01]*)\x01/g,
function(aMatch, aMsg) {
if (aMsg)
ctcpMessages.push(new CTCPMessage(aMessage, aMsg));
return "";
});
// If no CTCP messages were found, return false.
if (!ctcpMessages.length)
return false;
// If there's some message left, send it back through the IRC handlers after
// stripping out the CTCP information. I highly doubt this will ever happen,
// but just in case. ;)
if (otherMessage) {
let message = aMessage;
message.params.pop();
message.params.push(otherMessage);
ircHandlers.handleMessage(message);
}
let handled = true;
// Loop over each raw CTCP message.
for each (let message in ctcpMessages)
handled &= ircHandlers.handleCTCPMessage(this, message);
return handled;
}
// This is the the basic CTCP protocol.
var ctcpBase = {
// Parameters
name: "CTCP",
priority: ircHandlers.DEFAULT_PRIORITY,
// These represent CTCP commands.
commands: {
"ACTION": function(aMessage) {
// ACTION <text>
// Display message in conversation
this.getConversation(this.isMUCName(aMessage.params[0]) ?
aMessage.params[0] : aMessage.nickname)
.writeMessage(aMessage.nickname || aMessage.source,
"/me " + aMessage.ctcp.param,
{incoming: true});
return true;
},
// Used when an error needs to be replied with.
"ERRMSG": function(aMessage) {
ERROR(aMessage);
return false;
},
// Returns the user's full name, and idle time.
"FINGER": function(aMessage) false,
// Dynamic master index of what a client knows.
"CLIENTINFO": function(aMessage) false,
// Used to measure the delay of the IRC network between clients.
"PING": function(aMessage) {
if (aMessage.command == "PRIVMSG") {
// PING timestamp
// Received PING request, send PING response.
LOG("Received PING request from " + aMessage.nickname +
". Sending PING response: \"" + aMessage.ctcp.param + "\".");
this.sendCTCPMessage("PING", aMessage.ctcp.param, aMessage.nickname,
true);
}
else {
// PING timestamp
// Received PING response, display to the user.
let sentTime = new Date(aMessage.ctcp.param);
// The received timestamp is invalid
if (isNaN(sentTime)) {
WARN(aMessage.nickname +
" returned an invalid timestamp from a CTCP PING: " +
aMessage.ctcp.param);
return false;
}
// Find the delay in seconds.
let delay = (Date.now() - sentTime) / 1000;
this.getConversation(aMessage.nickname)
.writeMessage(aMessage.nickname,
_("ctcp.ping.response", delay, aMessage.nickname),
{system: true});
}
return true;
},
// An encryption protocol between clients without any known reference.
"SED": function(aMessage) false,
// Where to obtain a copy of a client.
"SOURCE": function(aMessage) false,
// Gets the local date and time from other clients.
"TIME": function(aMessage) {
if (aMessage.command == "PRIVMSG") {
// TIME
// Received a TIME request, send a human readable response.
let now = (new Date()).toString();
LOG("Received TIME request from " + aMessage.nickname +
". Sending TIME response: \"" + now + "\".");
this.sendCTCPMessage("TIME", ":" + now, aMessage.nickname, true);
}
else {
// TIME :<human-readable-time-string>
// Received a TIME reply, display it.
// Remove the : prefix, if it exists.
let time =
new Date(aMessage.ctcp.param.slice(aMessage.ctcp.param[0] == ":"));
// The received timestamp is invalid
if (isNaN(time)) {
WARN(aMessage.nickname +
" returned an invalid date format from CTCP TIME: " +
aMessage.ctcp.param);
return false;
}
this.getConversation(aMessage.nickname)
.writeMessage(aMessage.nickname,
_("ctcp.time.response", aMessage.nickname,
time.toLocaleString()), {system: true});
}
return true;
},
// A string set by the user (never the client coder)
"USERINFO": function(aMessage) false,
// The version and type of the client.
"VERSION": function(aMessage) {
if (aMessage.command == "PRIVMSG") {
// VERSION
// Received VERSION request, send VERSION response.
// Use brandShortName as the client version.
let version =
l10nHelper("chrome://branding/locale/brand.properties")("brandShortName");
LOG("Received VERSION request from " + aMessage.nickname +
". Sending VERSION response: \"" + version + "\".");
this.sendCTCPMessage("VERSION", version, aMessage.nickname, true);
}
else if (aMessage.command == "NOTICE" && aMessage.ctcp.param.length) {
// VERSION #:#:#
// Received VERSION response, display to the user.
let response = _("ctcp.version", aMessage.nickname,
aMessage.ctcp.param);
this.getConversation(aMessage.nickname)
.writeMessage(aMessage.nickname, response, {system: true});
}
return true;
}
}
};

Просмотреть файл

@ -0,0 +1,315 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This is to be exported directly onto the IRC prplIProtocol object, directly
// implementing the commands field before we register them.
const EXPORTED_SYMBOLS = ["commands"];
Components.utils.import("resource:///modules/ircUtils.jsm");
// Shortcut to get the JavaScript account object.
function getAccount(aConv) aConv.wrappedJSObject._account;
// Kick a user from a channel
// aMsg is <user> [comment]
function kickCommand(aMsg, aConv) {
if (!aMsg.length)
return false;
let params = [aConv.name];
let offset = aMsg.indexOf(" ");
if (offset != -1) {
params.push(aMsg.slice(0, offset));
params.push(aMsg.slice(offset + 1));
}
else
params.push(aMsg);
getAccount(aConv).sendMessage("KICK", params);
return true;
}
// Send a message directly to a user.
// aMsg is <user> <message>
function messageCommand(aMsg, aConv) {
let sep = aMsg.indexOf(" ");
// If no space in the message or the space is at the end of the message.
if (sep == -1 || (sep + 1) == aMsg.length)
return false;
return privateMessage(aConv, aMsg.slice(sep + 1), aMsg.slice(0, sep));
}
// aAdd is true to add a mode, false to remove a mode.
function setMode(aNickname, aConv, aMode, aAdd) {
if (!aNickname.length)
return false;
// Change the mode for each nick, as separator by spaces.
return aNickname.split(" ").every(function(aNick)
simpleCommand(aConv, "MODE",
[aConv.name, (aAdd ? "+" : "-") + aMode, aNick]));
}
function actionCommand(aMsg, aConv) {
if (!ctcpCommand(aConv, aConv.name, "ACTION", aMsg))
return false;
// Show the action on our conversation.
let account = getAccount(aConv);
account.getConversation(aConv.name)
.writeMessage(account._nickname, "/me " + aMsg, {outgoing: true});
return true;
}
// Helper functions
function privateMessage(aConv, aMsg, aNickname) {
if (!aMsg.length)
return false;
// This will open the conversation, send and display the text
getAccount(aConv).getConversation(aNickname).sendMsg(aMsg);
return true;
}
// This will send a command to the server, if no parameters are given, it is
// assumed that the command takes no parameters. aParams can be either a single
// string or an array of parameters.
function simpleCommand(aConv, aCommand, aParams) {
if (!aParams || !aParams.length)
getAccount(aConv).sendMessage(aCommand);
else
getAccount(aConv).sendMessage(aCommand, aParams);
return true;
}
function ctcpCommand(aConv, aTarget, aCommand, aMsg) {
if (!aTarget.length)
return false;
getAccount(aConv).sendCTCPMessage(aCommand, aMsg, aTarget, false);
return true;
}
var commands = [
{
name: "action",
get helpString() _("command.action", "action"),
run: actionCommand
},
{
name: "ctcp",
get helpString() _("command.ctcp", "ctcp"),
run: function(aMsg, aConv) {
let separator = aMsg.indexOf(" ");
if (separator == -1 && (separator + 1) != aMsg.length)
return false;
return ctcpCommand(aConv, aMsg.slice(0, separator),
aMsg.slice(separator + 1));
}
},
{
name: "chanserv",
get helpString() _("command.chanserv", "chanserv"),
run: function(aMsg, aConv) privateMessage(aConv, aMsg, "ChanServ")
},
{
name: "deop",
get helpString() _("command.deop", "deop"),
run: function(aMsg, aConv) setMode(aMsg, aConv, "o", false)
},
{
name: "devoice",
get helpString() _("command.devoice", "devoice"),
run: function(aMsg, aConv) setMode(aMsg, aConv, "v", false)
},
{
name: "invite",
get helpString() _("command.invite", "invite"),
run: function(aMsg, aConv) simpleCommand(aConv, "INVITE", aMsg)
},
{
name: "j",
get helpString() _("command.join", "j"),
run: function(aMsg, aConv) simpleCommand(aConv, "JOIN", aMsg)
},
{
name: "join",
get helpString() _("command.join", "join"),
run: function(aMsg, aConv) simpleCommand(aConv, "JOIN", aMsg)
},
{
name: "kick",
get helpString() _("command.kick", "kick"),
run: kickCommand
},
{
name: "list",
get helpString() _("command.list", "list"),
run: function(aMsg, aConv) simpleCommand(aConv, "LIST")
},
{
name: "me",
get helpString() _("command.action", "me"),
run: actionCommand
},
{
name: "memoserv",
get helpString() _("command.memoserv", "memoserv"),
run: function(aMsg, aConv) privateMessage(aConv, aMsg, "MemoServ")
},
{
name: "mode",
get helpString() _("command.mode", "mode"),
run: function(aMsg, aConv) simpleCommand(aConv, "MODE", aMsg)
},
{
name: "msg",
get helpString() _("command.msg", "msg"),
run: messageCommand
},
{
name: "nick",
get helpString() _("command.nick", "nick"),
run: function(aMsg, aConv) simpleCommand(aConv, "NICK", aMsg)
},
{
name: "nickserv",
get helpString() _("command.nickserv", "nickserv"),
run: function(aMsg, aConv) privateMessage(aConv, aMsg, "NickServ")
},
{
name: "notice",
get helpString() _("command.notice", "notice"),
run: function(aMsg, aConv) simpleCommand(aConv, "NOTICE", aMsg)
},
{
name: "op",
get helpString() _("command.op", "op"),
run: function(aMsg, aConv) setMode(aMsg, aConv, "o", true)
},
{
name: "operwall",
get helpString() _("command.wallops", "operwall"),
run: function(aMsg, aConv) simpleCommand(aConv, "WALLOPS", aMsg)
},
{
name: "operserv",
get helpString() _("command.operserv", "operserv"),
run: function(aMsg, aConv) privateMessage(aConv, aMsg, "OperServ")
},
{
name: "part",
get helpString() _("command.part", "part"),
run: function (aMsg, aConv) {
aConv.wrappedJSObject.part(aMsg);
return true;
}
},
{
name: "ping",
get helpString() _("command.ping", "ping"),
run: function(aMsg, aConv) ctcpCommand(aConv, aMsg, "PING")
},
{
name: "query",
get helpString() _("command.msg", "query"),
run: messageCommand
},
{
name: "quit",
get helpString() _("command.quit", "quit"),
run: function(aMsg, aConv) {
getAccount(aConv).quit(aMsg);
return true;
}
},
{
name: "quote",
get helpString() _("command.quote", "quote"),
run: function(aMsg, aConv) {
if (!aMsg.length)
return false;
getAccount(aConv).sendRawMessage(aMsg);
return true;
}
},
{
name: "remove",
get helpString() _("command.kick", "remove"),
run: kickCommand
},
{
name: "time",
get helpString() _("command.time", "time"),
run: function(aMsg, aConv) simpleCommand(aConv, "TIME")
},
{
name: "topic",
get helpString() _("command.topic", "topic"),
run: function(aMsg, aConv) {
aConv.topic = aMsg;
return true;
}
},
{
name: "umode",
get helpString() _("command.umode", "umode"),
run: function(aMsg, aConv) simpleCommand(aConv, "MODE", aMsg)
},
{
name: "version",
get helpString() _("command.version", "version"),
run: function(aMsg, aConv) ctcpCommand(aConv, aMsg, "VERSION")
},
{
name: "voice",
get helpString() _("command.voice", "voice"),
run: function(aMsg, aConv) setMode(aMsg, aConv, "v", true)
},
{
name: "wallops",
get helpString() _("command.wallops", "wallops"),
run: function(aMsg, aConv) simpleCommand(aConv, "WALLOPS", aMsg)
},
{
name: "whowas",
get helpString() _("command.whowas", "whowas"),
run: function(aMsg, aConv) simpleCommand(aConv, "WHOWAS", aMsg)
}
];

Просмотреть файл

@ -0,0 +1,100 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* This contains an implementation of the Direct Client-to-Client (DCC)
* protocol.
* A description of the DCC protocol
* http://www.irchelp.org/irchelp/rfc/dccspec.html
*/
const EXPORTED_SYMBOLS = ["ctcpDCC", "dccBase"];
const Cu = Components.utils;
Cu.import("resource:///modules/ircHandlers.jsm");
Cu.import("resource:///modules/ircUtils.jsm");
Cu.import("resource:///modules/jsProtoHelper.jsm");
// Parse a CTCP message into a DCC message. A DCC message is a CTCP message of
// the form:
// DCC <type> <argument> <address> <port> [<size>]
function DCCMessage(aMessage) {
let message = aMessage;
let params = message.ctcp.param.split(" ");
if (params.length < 4) {
ERROR("Not enough DCC parameters:\n" + JSON.stringify(aMessage));
return null;
}
try {
// Address, port and size should be treated as unsigned long, unsigned short
// and unsigned long, respectively. The protocol is designed to handle
// further arguements, if necessary.
message.ctcp.dcc = {
type: params[0],
argument: params[1],
address: new Number(params[2]),
port: new Number(params[3]),
size: params.length == 5 ? new Number(params[4]) : null,
furtherArguments: params.length > 5 ? params.slice(5) : []
};
} catch (e) {
ERROR("Error parsing DCC parameters:\n" + JSON.stringify(aMessage));
return null;
}
return message;
}
// This is the DCC handler for CTCP, it will call each DCC handler.
var ctcpDCC = {
name: "DCC",
// Slightly above default CTCP priority.
priority: ircHandlers.HIGH_PRIORITY + 10,
commands: {
// Handle a DCC message by parsing the message and executing any handlers.
"DCC": function(aMessage) {
// If there are no DCC handlers, then don't parse the DCC message.
if (!ircHandlers.hasDCCHandlers)
return false;
// Parse the message and attempt to handle it.
return ircHandlers.handleMessage(this, DCCMessage(aMessage));
}
}
};

Просмотреть файл

@ -0,0 +1,153 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ["ircHandlers"];
const Cu = Components.utils;
Cu.import("resource:///modules/imXPCOMUtils.jsm");
Cu.import("resource:///modules/ircUtils.jsm");
var ircHandlers = {
/*
* Object to hold the IRC handlers, each handler is an object that implements:
* name The display name of the handler.
* priority The priority of the handler (0 is default, positive is
* higher priority)
* commands An object of commands, each command is a function which
* accepts a message object and has 'this' bound to the account
* object. It should return whether the message was successfully
* handler or not.
*/
_ircHandlers: [],
// Object to hold the ISUPPORT handlers, expects the same fields as
// _ircHandlers.
_isupportHandlers: [],
// Object to hold the CTCP handlers, expects the same fields as _ircHandlers.
_ctcpHandlers: [],
// Object to hold the DCC handlers, expects the same fields as _ircHandlers.
_dccHandlers: [],
_registerHandler: function(aArray, aHandler) {
// Protect ourselves from adding broken handlers.
if (!("commands" in aHandler)) {
ERROR("IRC handlers must have a \"commands\" property: " + aHandler.name);
return false;
}
aArray.push(aHandler);
aArray.sort(function(a, b) b.priority - a.priority);
return true;
},
_unregisterHandler: function(aArray, aHandler) {
aArray = aArray.filter(function(h) h.name != aHandler.name);
},
registerHandler: function(aHandler)
this._registerHandler(this._ircHandlers, aHandler),
unregisterHandler: function(aHandler)
this._unregisterHandler(this._ircHandlers, aHandler),
registerISUPPORTHandler: function(aHandler)
this._registerHandler(this._isupportHandlers, aHandler),
unregisterISUPPORTHandler: function(aHandler)
this._unregisterHandler(this._isupportHandlers, aHandler),
registerCTCPHandler: function(aHandler)
this._registerHandler(this._ctcpHandlers, aHandler),
unregisterCTCPHandler: function(aHandler)
this._unregisterHandler(this._ctcpHandlers, aHandler),
registerDCCHandler: function(aHandler)
this._registerHandler(this._dccHandlers, aHandler),
unregisterDCCHandler: function(aHandler)
this._unregisterHandler(this._dccHandlers, aHandler),
// Handle a message based on a set of handlers.
_handleMessage: function(aHandlers, aAccount, aMessage, aCommand) {
// Loop over each handler and run the command until one handles the message.
for each (let handler in aHandlers) {
try {
// Attempt to execute the command, by checking if the handler has the
// command.
// Parse the command with the JavaScript account object as "this".
if (hasOwnProperty(handler.commands, aCommand) &&
handler.commands[aCommand].call(aAccount, aMessage)) {
DEBUG(JSON.stringify(aMessage));
return true;
}
} catch (e) {
// We want to catch an error here because one of our handlers are broken,
// if we don't catch the error, the whole IRC plug-in will die.
ERROR("Error running command " + aCommand + " with handler " +
handler.name + ":\n" + JSON.stringify(aMessage));
Cu.reportError(e);
}
}
return false;
},
handleMessage: function(aAccount, aMessage)
this._handleMessage(this._ircHandlers, aAccount, aMessage,
aMessage.command.toUpperCase()),
handleISUPPORTMessage: function(aAccount, aMessage)
this._handleMessage(this._isupportHandlers, aAccount, aMessage,
aMessage.isupport.parameter),
// aMessage is a CTCP Message, which inherits from an IRC Message.
handleCTCPMessage: function(aAccount, aMessage)
this._handleMessage(this._ctcpHandlers, aAccount, aMessage,
aMessage.ctcp.command),
// aMessage is a DCC Message, which inherits from a CTCP Message.
handleDCCPMessage: function(aAccount, aMessage)
this._handleMessage(this._dccHandlers, aAccount, aMessage,
aMessage.ctcp.dcc.type),
// Checking if handlers exist.
get hasHandlers() this._ircHandlers.length > 0,
get hasISUPPORTHandlers() this._isupportHandlers.length > 0,
get hasCTCPHandlers() this._ctcpHandlers.length > 0,
get hasDCCHandlers() this._dccHandlers.length > 0,
// Some constant priorities.
get LOW_PRIORITY() -100,
get DEFAULT_PRIORITY() 0,
get HIGH_PRIORITY() 100
}

Просмотреть файл

@ -0,0 +1,255 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* This implements the ISUPPORT parameters for the 005 numeric to allow a server
* to notify a client of what capabilities it supports.
* The 005 numeric
* http://www.irc.org/tech_docs/005.html
* RFC Drafts: IRC RPL_ISUPPORT Numeric Definition
* http://tools.ietf.org/html/draft-brocklesby-irc-isupport-03
* http://tools.ietf.org/html/draft-hardy-irc-isupport-00
*/
const EXPORTED_SYMBOLS = ["ircISUPPORT"];
const Cu = Components.utils;
Cu.import("resource:///modules/ircHandlers.jsm");
Cu.import("resource:///modules/ircUtils.jsm");
function isupportMessage(aMessage, aToken) {
let message = aMessage;
message.isupport = {};
message.isupport.useDefault = aToken[0] == "-";
let token = message.isupport.useDefault ? aToken.slice(1) : aToken;
[message.isupport.parameter, message.isupport.value] = token.split("=");
return message;
}
var ircISUPPORT = {
name: "ISUPPORT",
// Slightly above default RFC 2812 priority.
priority: ircHandlers.DEFAULT_PRIORITY + 10,
commands: {
// RPL_ISUPPORT
// [-]<parameter>[=<value>] :are supported by this server
"005": function(aMessage) {
if (!("ISUPPORT" in this))
this.ISUPPORT = {};
// Seperate the ISUPPORT parameters.
let tokens = aMessage.params[1].split(" ");
let handled = true;
for (let token in tokens) {
let message = isupportMessage(aMessage, token);
handled &= ircHandlers.handleISUPPORTMessage(this, message);
}
return handled;
}
}
}
function setSimpleNumber(aAccount, aField, aMessage, aDefaultValue) {
let value =
aMessage.isupport.value ? new Number(aMessage.isupport.value) : null;
aAccount[aField] = (value && !isNaN(value)) ? value : aDefaultValue;
return true;
}
// Generates a function that will set the ASCII range of aStart-aEnd as the
// uppercase of (aStart-aEnd) + 0x20.
function generateNormalize(aStart, aEnd) {
const exp = new RegExp("[\\x" + aStart.toString(16) + "-\\x" +
aEnd.toString(16) + "]", "g");
return function(aStr, aRemoveStatus) {
let str = aStr;
if (aPrefixes && aPrefixes.indexOf(aStr[0]) != -1)
str = str.slice(1);
return str.replace(exp,
function(c) String.fromCharCode(c.charCodeAt(0) + 0x20));
};
}
var isupportBase = {
name: "ISUPPORT",
description: "IRC RPL_ISUPPORT Numeric Definition",
priority: ircHandlers.DEFAULT_PRIORITY,
commands: {
"CASEMAPPING": function(aMessage) {
// CASEMAPPING=<mapping>
// Allows the server to specify which method it uses to compare equality
// of case-insensitive strings.
// By default, use rfc1459 type case mapping.
let value = aMessage.isupport.useDefault ?
"rfc1493" : aMessage.isupport.value;
// Set the normalize function of the account to use the proper case
// mapping.
if (value == "ascii") {
// The ASCII characters 97 to 122 (decimal) are the lower-case
// characters of ASCII 65 to 90 (decimal).
this.normalize = generateNormalize(65, 90);
}
else if (value == "rfc1493") {
// The ASCII characters 97 to 126 (decimal) are the lower-case
// characters of ASCII 65 to 94 (decimal).
this.normalize = generateNormalize(65, 94);
}
else if (value == "strict-rfc1459") {
// The ASCII characters 97 to 125 (decimal) are the lower-case
// characters of ASCII 65 to 93 (decimal).
this.normalize = generateNormalize(65, 93);
}
return true;
},
"CHANLIMIT": function(aMessage) {
// CHANLIMIT=<prefix>:<number>[,<prefix>:<number>]*
// Note that each <prefix> can actually contain multiple prefixes, this
// means the sum of those prefixes is given.
this.maxChannels = {};
let pairs = aMessage.isupport.value.split(",");
for each (let pair in pairs) {
let [prefixes, num] = pair.split(":");
this.maxChannels[prefix] = num;
}
return true;
},
"CHANMODES": function(aMessage) false,
"CHANNELLEN": function(aMessage) {
// CHANNELLEN=<number>
// Default is from RFC 1493.
return setSimpleNumber(this, "maxChannelLength", aMessage, 200);
},
"CHANTYPES": function(aMessage) {
// CHANTYPES=[<channel prefix>]*
let value = aMessage.isupport.useDefault ? "#&" : aMessage.isupport.value;
this.channelPrefixes = value.split("");
return true;
},
"EXCEPTS": function(aMessage) false,
"IDCHAN": function(aMessage) false,
"INVEX": function(aMessage) false,
"KICKLEN": function(aMessage) {
// KICKLEN=<number>
// Default value is Infinity.
return setSimpleNumber(this, "maxKickLength", aMessage, Infinity);
},
"MAXLIST": function(aMessage) false,
"MODES": function(aMessage) false,
"NETWORK": function(aMessage) false,
"NICKLEN": function(aMessage) {
// NICKLEN=<number>
// Default value is from RFC 1493.
return setSimpleNumber(this, "maxNicknameLength", aMessage, 9);
},
"PREFIX": function(aMessage) {
// PREFIX=[(<mode character>*)<prefix>*]
let value =
aMessage.isupport.useDefault ? "(ov)@+" : aMessage.isupport.value;
this.userPrefixToModeMap = {};
// A null value specifier indicates that no prefixes are supported.
if (!value.length)
return true;
let matches = /\(([a-z]*)\)(.*)/i.exec(value);
if (!matches) {
// The pattern doesn't match.
WARN("Invalid PREFIX value: " + value);
return false;
}
if (matches[1].length != matches[2].length) {
WARN("Invalid PREFIX value, does not provide one-to-one mapping:" +
value);
return false;
}
for (let i = 0; i < matches[2].length; i++)
this.userPrefixToModeMap[matches[2][i]] = matches[1][i];
return true;
},
"SAFELIST": function(aMessage) false,
"STATUSMSG": function(aMessage) false,
"STD": function(aMessage) {
// This was never updated as the RFC was never formalized.
if (aMessage.isupport.value != "rfcnnnn")
WARN("Unknown ISUPPORT numeric form: " + aMessage.isupport.value);
return true;
},
"TARGMAX": function(aMessage) {
// TARGMAX=<command>:<max targets>[,<command>:<max targets>]*
if (aMessage.isupport.useDefault) {
this.maxTargets = 1;
return true;
}
this.maxTargets = {};
let commands = aMessage.isupport.value.split(",");
for (let i = 0; i < commands.length; i++) {
let [command, limitStr] = commands[i].split("=");
let limit = limitStr ? new Number(limit) : Infinity;
if (isNaN(limit)) {
WARN("Invalid maximum number of targets: " + limitStr);
continue;
}
this.maxTargets[command] = limit;
}
return true;
},
"TOPICLEN": function(aMessage) {
// TOPICLEN=<number>
// Default value is Infinity.
return setSimpleNumber(this, "maxTopicLength", aMessage, Infinity);
},
// The following are considered "obsolete" by the RFC, but are still in use.
"CHARSET": function(aMessage) false,
"MAXBANS": function(aMessage) false,
"MAXCHANNELS": function(aMessage) false,
"MAXTARGETS": function(aMessage) {
return setSimpleNumber(this, "maxTargets", aMessage, 1);
}
}
};

Просмотреть файл

@ -0,0 +1,207 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Instantbird.
*
* The Initial Developer of the Original Code is
* Patrick Cloke <clokep@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark "Mook" Yen <Mook.moz+Instantbird.code@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ["DEBUG", "LOG", "WARN", "ERROR", "_",
"ctcpFormatToText", "ctcpFormatToHTML"];
const {classes: Cc, interfaces: Ci} = Components;
Components.utils.import("resource:///modules/imXPCOMUtils.jsm");
initLogModule("irc", this);
XPCOMUtils.defineLazyGetter(this, "_", function()
l10nHelper("chrome://chat/locale/irc.properties")
);
XPCOMUtils.defineLazyGetter(this, "TXTToHTML", function() {
let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(Ci.mozITXTToHTMLConv);
return function(aTXT) cs.scanTXT(aTXT, cs.kEntities);
});
// The supported formatting control characters, as described as deprecated in
// http://www.invlogic.com/irc/ctcp.html#3.11
const CTCP_TAGS = {"\x02": "b", // \002, ^B, Bold
"\x16": "i", // \026, ^V, Reverse or Inverse (Italics)
"\x1F": "u", // \037, ^_, Underline
"\x03": mIRCColoring, // \003, ^C, Coloring
"\x0F": null}; // \017, ^O, Clear all formatting
// Generate an expression that will search for any of the control characters.
const CTCP_TAGS_STRING = "[" + Object.keys(CTCP_TAGS).join("") + "]";
const CTCP_TAGS_EXP = new RegExp(CTCP_TAGS_STRING);
const CTCP_TAGS_EXP_GLOBAL = new RegExp(CTCP_TAGS_STRING, "g");
// Remove all CTCP formatting characters.
function ctcpFormatToText(aString) aString.replace(CTCP_TAGS_EXP_GLOBAL, "")
// Close the tags in the opposite order they were opened.
function closeStack(aStack)
aStack.reverse().map(function(aTag) "</" + aTag.split(" ", 1) + ">").join("")
/**
* Convert a string from CTCP escaped formatting to HTML markup.
* @param aString the string with CTCP formatting to parse
* @return The HTML output string
*/
function ctcpFormatToHTML(aString) {
let next,
stack = [],
input = TXTToHTML(aString),
output = "",
length;
while ((next = CTCP_TAGS_EXP.exec(input))) {
if (next.index > 0)
output += input.substr(0, next.index);
length = 1;
let tag = CTCP_TAGS[input[next.index]];
if (tag === null) {
// Clear all formatting.
output += closeStack(stack);
stack = [];
}
else if (typeof tag == "function") {
[stack, output, length] = tag(stack, input.substr(next.index), output);
}
else {
let offset = stack.indexOf(tag);
if (offset == -1) {
// Tag not found; open new tag.
output += "<" + tag + ">";
stack.push(tag);
}
else {
// Tag found; close existing tag (and all tags after it).
output += closeStack(stack.slice(offset));
// Reopen the tags that came after it.
stack.slice(offset + 1)
.forEach(function(aTag) output += "<" + aTag + ">");
// Remove the tag from the stack.
stack.splice(offset, 1);
}
}
// Avoid infinite loops, if.
length = (length <= 0) ? 1 : length;
// Skip to after the last match.
input = input.substr(next.index + length);
}
// Return unmatched bits and close any open tags at the end.
return output + input + closeStack(stack);
}
// mIRC colors are defined at http://www.mirc.com/colors.html.
// This expression matches \003<one or two digits>[,<one or two digits>].
const M_IRC_COLORS_EXP = /^\x03(?:(\d\d?)(?:,(\d\d?))?)?/;
const M_IRC_COLOR_MAP = {
"0": "white",
"1": "black",
"2": "navy", // blue (navy)
"3": "green",
"4": "red",
"5": "maroon", // brown (maroon)
"6": "purple",
"7": "orange", // orange (olive)
"8": "yellow",
"9": "lime", // light green (lime)
"10": "teal", // teal (a green/blue cyan)
"11": "aqua", // light cyan (cyan) (aqua)
"12": "blue", // light blue (royal)",
"13": "fuchsia", // pink (light purple) (fuchsia)
"14": "grey",
"15": "silver", // light grey (silver)
"99": "transparent"
};
function mIRCColoring(aStack, aInput, aOutput) {
function getColor(aKey) {
let key = aKey;
// Single digit numbers can (must?) be prefixed by a zero.
if (key.length == 2 && key[0] == "0")
key = key[1];
if (M_IRC_COLOR_MAP.hasOwnProperty(key))
return M_IRC_COLOR_MAP[key];
return null;
}
let matches,
stack = aStack,
input = aInput,
output = aOutput,
length = 1;
if ((matches = M_IRC_COLORS_EXP.exec(input))) {
let format = ["font"];
if (!matches[1]) {
// Find the first font tag.
let offset = stack.map(function(aTag) aTag.indexOf("font") == 0)
.indexOf(true);
// Close all tags after the first font tag.
output += closeStack(stack.slice(offset));
// Remove the font tags from the stack.
stack = stack.filter(function(aTag) aTag.indexOf("font"));
// Reopen the other tags.
stack.slice(offset)
.forEach(function(aTag) output += "<" + aTag + ">");
}
else {
// The foreground color.
let color = getColor(matches[1]);
if (color)
format.push("color=\"" + color + "\"");
// The background color.
if (matches[2]) {
let color = getColor(matches[2]);
if (color)
format.push("background=\"" + color + "\"");
}
if (format.length > 1) {
output += "<" + format.join(" ") + ">";
stack.push(format.join(" "));
length = matches[0].length;
}
}
}
return [stack, output, length];
}

Просмотреть файл

@ -0,0 +1,5 @@
chat.jar:
% skin prpl-irc classic/1.0 %skin/classic/prpl/irc/
skin/classic/prpl/irc/icon32.png (icons/prpl-irc-32.png)
skin/classic/prpl/irc/icon48.png (icons/prpl-irc-48.png)
skin/classic/prpl/irc/icon.png (icons/prpl-irc.png)

Просмотреть файл

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource:///modules/ircUtils.jsm");
const input = [
// From http://www.mirc.com/colors.html
"\x035,12colored text and background\x03",
"\x035colored text\x03",
"\x033colored text \x035,2more colored text and background\x03",
"\x033,5colored text and background \x038other colored text but same background\x03",
"\x033,5colored text and background \x038,7other colored text and different background\x03",
// Based on above, but more complicated.
"\x02\x035,12colored \x1Ftext and background\x03. You sure about this?",
// Implied by above.
"So a \x03,8 attribute is not valid and thus ignored.",
// Try some of the above with two digits.
"\x0303,5colored text and background \x0308other colored text but same background\x03",
"\x0303,05colored text and background \x038,7other colored text and different background\x03",
];
function run_test() {
add_test(test_mIRCColoring);
//add_test(test_ctcpFormatToText);
run_next_test();
}
function test_mIRCColoring() {
let expectedOutput = [
"<font color=\"maroon\" background=\"blue\">colored text and background</font>",
"<font color=\"maroon\">colored text</font>",
"<font color=\"green\">colored text <font color=\"maroon\" background=\"navy\">more colored text and background</font></font>",
"<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\">other colored text but same background</font></font>",
"<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\" background=\"orange\">other colored text and different background</font></font>",
"<b><font color=\"maroon\" background=\"blue\">colored <u>text and background</u></font><u>. You sure about this?</u></b>",
"So a ,8 attribute is not valid and thus ignored.",
"<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\">other colored text but same background</font></font>",
"<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\" background=\"orange\">other colored text and different background</font></font>"
];
let output = input.map(ctcpFormatToHTML);
for (let i = 0; i < output.length; i++)
do_check_eq(expectedOutput[i], output[i]);
run_next_test();
}
function test_ctcpFormatToText() {
let expectedOutput = "The quick brown fox jumps over the lazy dog.";
let output = input.map(ctcpFormatToText);
for (let i = 0; i < output.length; i++)
do_check_eq(expectedOutput, output[i]);
run_next_test();
}
function test_mIRCColor() {
let expectedOutput = "The quick brown fox jumps over the lazy dog.";
let output = input.map(ctcpFormatToText);
for (let i = 0; i < output.length; i++)
do_check_eq(expectedOutput, output[i]);
run_next_test();
}

Просмотреть файл

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource:///modules/ircUtils.jsm");
//TODO add a test for special JS characters (|, etc...)
const input = [
"The quick brown fox \x02jumps\x02 over the lazy dog.",
"The quick brown fox \x02jumps\x0F over the lazy dog.",
"The quick brown \x16fox jumps\x16 over the lazy dog.",
"The quick brown \x16fox jumps\x0F over the lazy dog.",
"The quick \x1Fbrown fox jumps over the lazy\x1F dog.",
"The quick \x1Fbrown fox jumps over the lazy\x0F dog.",
"The quick \x1Fbrown fox \x02jumps over the lazy\x1F dog.",
"The quick \x1Fbrown fox \x02jumps\x1F over the lazy\x02 dog.",
"The quick \x1Fbrown \x16fox \x02jumps\x1F over\x16 the lazy\x02 dog.",
"The quick \x1Fbrown \x16fox \x02jumps\x0F over \x16the lazy \x02dog."
];
function run_test() {
add_test(test_ctcpFormatToHTML);
add_test(test_ctcpFormatToText);
run_next_test();
}
function test_ctcpFormatToHTML() {
let expectedOutput = [
"The quick brown fox <b>jumps</b> over the lazy dog.",
"The quick brown fox <b>jumps</b> over the lazy dog.",
"The quick brown <i>fox jumps</i> over the lazy dog.",
"The quick brown <i>fox jumps</i> over the lazy dog.",
"The quick <u>brown fox jumps over the lazy</u> dog.",
"The quick <u>brown fox jumps over the lazy</u> dog.",
"The quick <u>brown fox <b>jumps over the lazy</b></u><b> dog.</b>",
"The quick <u>brown fox <b>jumps</b></u><b> over the lazy</b> dog.",
"The quick <u>brown <i>fox <b>jumps</b></i></u><i><b> over</b></i><b> the lazy</b> dog.",
"The quick <u>brown <i>fox <b>jumps</b></i></u> over <i>the lazy <b>dog.</b></i>"
];
let output = input.map(ctcpFormatToHTML);
for (let i = 0; i < output.length; i++)
do_check_eq(expectedOutput[i], output[i]);
run_next_test();
}
function test_ctcpFormatToText() {
let expectedOutput = "The quick brown fox jumps over the lazy dog.";
let output = input.map(ctcpFormatToText);
for (let i = 0; i < output.length; i++)
do_check_eq(expectedOutput, output[i]);
run_next_test();
}

Просмотреть файл

@ -0,0 +1,6 @@
[DEFAULT]
head =
tail =
[test_ctcpFormatting.js]
[test_ctcpColoring.js]

Просмотреть файл

@ -287,3 +287,5 @@ pref("browser.tabs.tabClipWidth", 140);
// 2 no close buttons at all // 2 no close buttons at all
// 3 at the end of the tabstrip // 3 at the end of the tabstrip
pref("browser.tabs.closeButtons", 1); pref("browser.tabs.closeButtons", 1);
pref("chat.irc.defaultQuitMessage", "Instantbird -- http://www.instantbird.com");

Просмотреть файл

@ -292,11 +292,7 @@
.scanTXT(aMsg, 0); .scanTXT(aMsg, 0);
var account = this._conv.account; var account = this._conv.account;
if (account.noNewlines) if (account.HTMLEnabled) {
// 'Illegal operation on WrappedNative prototype object' if the this
// object is not specified (since nsIClassInfo was added to this._conv)
msg.split("\n").forEach(this._conv.sendMsg, this._conv);
else if (account.HTMLEnabled) {
msg = msg.replace(/\n/g, "<br/>"); msg = msg.replace(/\n/g, "<br/>");
if (Services.prefs.getBoolPref("messenger.conversations.sendFormat")) { if (Services.prefs.getBoolPref("messenger.conversations.sendFormat")) {
let style = MessageFormat.getMessageStyle(); let style = MessageFormat.getMessageStyle();
@ -352,8 +348,17 @@
} }
this._conv.sendMsg(msg); this._conv.sendMsg(msg);
} }
else else {
this._conv.sendMsg(account.HTMLEscapePlainText ? msg : aMsg); msg = account.HTMLEscapePlainText ? msg : aMsg;
if (account.noNewlines) {
// 'Illegal operation on WrappedNative prototype object' if the this
// object is not specified (since this._conv implements nsIClassInfo)
msg.split("\n").forEach(this._conv.sendMsg, this._conv);
}
else
this._conv.sendMsg(msg);
}
// reset the textbox to its original size // reset the textbox to its original size
this.resetInput(); this.resetInput();
]]> ]]>

Просмотреть файл

@ -432,6 +432,8 @@
@BINPATH@/components/ibCommandLineHandler.manifest @BINPATH@/components/ibCommandLineHandler.manifest
@BINPATH@/components/ibStatusCommandLineHandler.js @BINPATH@/components/ibStatusCommandLineHandler.js
@BINPATH@/components/ibStatusCommandLineHandler.manifest @BINPATH@/components/ibStatusCommandLineHandler.manifest
@BINPATH@/components/irc.js
@BINPATH@/components/irc.manifest
@BINPATH@/components/facebook.js @BINPATH@/components/facebook.js
@BINPATH@/components/facebook.manifest @BINPATH@/components/facebook.manifest
@BINPATH@/components/gtalk.js @BINPATH@/components/gtalk.js
@ -460,7 +462,7 @@
@BINPATH@/defaults/profile/prefs.js @BINPATH@/defaults/profile/prefs.js
; [Layout Engine Resources] ; [Layout Engine Resources]
; Style Sheets, Graphics and other Resources used by the layout engine. ; Style Sheets, Graphics and other Resources used by the layout engine.
@BINPATH@/res/EditorOverride.css @BINPATH@/res/EditorOverride.css
@BINPATH@/res/contenteditable.css @BINPATH@/res/contenteditable.css
@BINPATH@/res/designmode.css @BINPATH@/res/designmode.css

Просмотреть файл

@ -1 +1,2 @@
[include:chat/protocols/irc/test/xpcshell.ini]
[include:purple/purplexpcom/src/test/xpcshell.ini] [include:purple/purplexpcom/src/test/xpcshell.ini]