Bug 953944 - Implement IRC in JavaScript, r=florian.
This commit is contained in:
Родитель
add113cd19
Коммит
e9b4146acb
|
@ -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
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 695 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1003 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 454 B |
|
@ -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]
|
||||||
|
|
Загрузка…
Ссылка в новой задаче