diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/chatzilla/xul/content/commands.js | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/chatzilla/xul/content/commands.js')
-rw-r--r-- | comm/suite/chatzilla/xul/content/commands.js | 4760 |
1 files changed, 4760 insertions, 0 deletions
diff --git a/comm/suite/chatzilla/xul/content/commands.js b/comm/suite/chatzilla/xul/content/commands.js new file mode 100644 index 0000000000..a99ca52c3e --- /dev/null +++ b/comm/suite/chatzilla/xul/content/commands.js @@ -0,0 +1,4760 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const CMD_CONSOLE = 0x01; +const CMD_NEED_NET = 0x02; +const CMD_NEED_SRV = 0x04; +const CMD_NEED_CHAN = 0x08; +const CMD_NEED_USER = 0x10; + +function initCommands() +{ + // Keep this in sync with the command.js section in chatzilla.properties. + var cmdary = + [/* "real" commands */ + ["about", cmdAbout, CMD_CONSOLE], + ["alias", cmdAlias, CMD_CONSOLE, + "[<alias-name> [<command-list>]]"], + ["attach", cmdAttach, CMD_CONSOLE, + "<irc-url>"], + ["away", cmdAway, CMD_CONSOLE, + "[<reason>]"], + ["back", cmdAway, CMD_CONSOLE], + ["ban", cmdBanOrExcept, CMD_NEED_CHAN | CMD_CONSOLE, + "[<nickname>]"], + ["cancel", cmdCancel, CMD_CONSOLE], + ["charset", cmdCharset, CMD_CONSOLE, + "[<new-charset>]"], + ["channel-motif", cmdMotif, CMD_NEED_CHAN | CMD_CONSOLE, + "[<motif> [<channel>]]"], + ["channel-pref", cmdPref, CMD_NEED_CHAN | CMD_CONSOLE, + "[<pref-name> [<pref-value>]]"], + ["cmd-undo", "cmd-docommand cmd_undo", 0], + ["cmd-redo", "cmd-docommand cmd_redo", 0], + ["cmd-cut", "cmd-docommand cmd_cut", 0], + ["cmd-copy", "cmd-docommand cmd_copy", 0], + ["cmd-paste", "cmd-docommand cmd_paste", 0], + ["cmd-delete", "cmd-docommand cmd_delete", 0], + ["cmd-selectall", "cmd-docommand cmd_selectAll", 0], + ["cmd-copy-link-url", "cmd-docommand cmd_copyLink", 0, + "<url>"], + ["cmd-mozilla-prefs", "cmd-docommand cmd_mozillaPrefs", 0], + ["cmd-prefs", "cmd-docommand cmd_chatzillaPrefs", 0], + ["cmd-chatzilla-prefs", "cmd-docommand cmd_chatzillaPrefs", 0], + ["cmd-chatzilla-opts", "cmd-docommand cmd_chatzillaPrefs", 0], + ["cmd-docommand", cmdDoCommand, 0, + "<cmd-name>"], + ["create-tab-for-view", cmdCreateTabForView, 0, + "<view>"], + ["custom-away", cmdAway, 0], + ["op", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<...>]"], + ["dcc-accept", cmdDCCAccept, CMD_CONSOLE, + "[<nickname> [<type> [<file>]]]"], + ["dcc-accept-list", cmdDCCAutoAcceptList, CMD_NEED_NET | CMD_CONSOLE], + ["dcc-accept-list-add", cmdDCCAutoAcceptAdd, + CMD_NEED_NET | CMD_CONSOLE, + "<nickname>"], + ["dcc-accept-list-remove", cmdDCCAutoAcceptDel, + CMD_NEED_NET | CMD_CONSOLE, + "<nickname>"], + ["dcc-chat", cmdDCCChat, CMD_NEED_SRV | CMD_CONSOLE, + "[<nickname>]"], + ["dcc-close", cmdDCCClose, CMD_CONSOLE, + "[<nickname> [<type> [<file>]]]"], + ["dcc-decline", cmdDCCDecline, CMD_CONSOLE, + "[<nickname>]"], + ["dcc-list", cmdDCCList, CMD_CONSOLE, + "[<type>]"], + ["dcc-send", cmdDCCSend, CMD_NEED_SRV | CMD_CONSOLE, + "[<nickname> [<file>]]"], + ["dcc-show-file", cmdDCCShowFile, CMD_CONSOLE, + "<file>"], + ["delayed", cmdDelayed, CMD_CONSOLE, + "<delay> <rest>"], + ["deop", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<...>]"], + ["describe", cmdDescribe, CMD_NEED_SRV | CMD_CONSOLE, + "<target> <action>"], + ["hop", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<...>]"], + ["dehop", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<...>]"], + ["voice", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<...>]"], + ["devoice", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<...>]"], + ["clear-view", cmdClearView, CMD_CONSOLE, + "[<view>]"], + ["client", cmdClient, CMD_CONSOLE], + ["commands", cmdCommands, CMD_CONSOLE, + "[<pattern>]"], + ["ctcp", cmdCTCP, CMD_NEED_SRV | CMD_CONSOLE, + "<target> <code> [<params>]"], + ["default-charset", cmdCharset, CMD_CONSOLE, + "[<new-charset>]"], + ["delete-view", cmdDeleteView, CMD_CONSOLE, + "[<view>]"], + ["desc", cmdDesc, CMD_CONSOLE, + "[<description>]"], + ["disable-plugin", cmdDisablePlugin, CMD_CONSOLE], + ["disconnect", cmdDisconnect, CMD_NEED_SRV | CMD_CONSOLE, + "[<reason>]"], + ["disconnect-all", cmdDisconnectAll, CMD_CONSOLE, + "[<reason>]"], + ["echo", cmdEcho, CMD_CONSOLE, + "<message>"], + ["edit-networks", cmdEditNetworks, CMD_CONSOLE], + ["enable-plugin", cmdEnablePlugin, CMD_CONSOLE, + "<plugin>"], + ["eval", cmdEval, CMD_CONSOLE, + "<expression>"], + ["evalsilent", cmdEval, CMD_CONSOLE, + "<expression>"], + ["except", cmdBanOrExcept, CMD_NEED_CHAN | CMD_CONSOLE, + "[<nickname>]"], + ["find", cmdFind, 0, + "[<rest>]"], + ["find-again", cmdFindAgain, 0], + ["focus-input", cmdFocusInput, 0], + ["font-family", cmdFont, CMD_CONSOLE, + "[<font>]"], + ["font-family-other", cmdFont, 0], + ["font-size", cmdFont, CMD_CONSOLE, + "[<font-size>]"], + ["font-size-other", cmdFont, 0], + ["goto-startup", cmdGotoStartup, CMD_CONSOLE], + ["goto-url", cmdGotoURL, 0, + "<url> [<anchor>]"], + ["goto-url-newwin", cmdGotoURL, 0, + "<url> [<anchor>]"], + ["goto-url-newtab", cmdGotoURL, 0, + "<url> [<anchor>]"], + ["help", cmdHelp, CMD_CONSOLE, + "[<pattern>]"], + ["hide-view", cmdHideView, CMD_CONSOLE, + "[<view>]"], + ["identify", cmdIdentify, CMD_NEED_SRV | CMD_CONSOLE, + "[<password>]"], + ["idle-away", cmdAway, 0], + ["idle-back", cmdAway, 0], + ["ignore", cmdIgnore, CMD_NEED_NET | CMD_CONSOLE, + "[<mask>]"], + ["input-text-direction", cmdInputTextDirection, 0, + "<dir>"], + ["install-plugin", cmdInstallPlugin, CMD_CONSOLE, + "[<url> [<name>]]"], + ["invite", cmdInvite, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<channel-name>]"], + ["join", cmdJoin, CMD_NEED_SRV | CMD_CONSOLE, + "[<channel-name> [<key>]]"], + ["join-charset", cmdJoin, CMD_NEED_SRV | CMD_CONSOLE, + "[<channel-name> <charset> [<key>]]"], + ["jump-to-anchor", cmdJumpToAnchor, CMD_NEED_NET, + "<anchor> [<channel-name>]"], + ["kick", cmdKick, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<reason>]"], + ["kick-ban", cmdKick, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname> [<reason>]"], + ["knock", cmdKnock, CMD_NEED_SRV | CMD_CONSOLE, + "<channel-name> [<reason>]"], + ["leave", cmdLeave, CMD_NEED_NET | CMD_CONSOLE, + "[<channel-name>] [<reason>]"], + ["links", cmdSimpleCommand, CMD_NEED_SRV | CMD_CONSOLE], + ["list", cmdList, CMD_NEED_SRV | CMD_CONSOLE, + "[<channel-name>]"], + ["list-plugins", cmdListPlugins, CMD_CONSOLE, + "[<plugin>]"], + ["load", cmdLoad, CMD_CONSOLE, + "<url>"], + ["log", cmdLog, CMD_CONSOLE, + "[<state>]"], + ["map", cmdSimpleCommand, CMD_NEED_SRV | CMD_CONSOLE], + ["marker", cmdMarker, CMD_CONSOLE], + ["marker-clear", cmdMarker, CMD_CONSOLE], + ["marker-set", cmdMarker, CMD_CONSOLE], + ["match-users", cmdMatchUsers, CMD_NEED_CHAN | CMD_CONSOLE, + "<mask>"], + ["me", cmdMe, CMD_CONSOLE, + "<action>"], + ["motd", cmdSimpleCommand, CMD_NEED_SRV | CMD_CONSOLE], + ["mode", cmdMode, CMD_NEED_SRV | CMD_CONSOLE, + "[<target>] [<modestr> [<param> [<...>]]]"], + ["motif", cmdMotif, CMD_CONSOLE, + "[<motif>]"], + ["msg", cmdMsg, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> <message>"], + ["name", cmdName, CMD_CONSOLE, + "[<username>]"], + ["names", cmdNames, CMD_NEED_SRV | CMD_CONSOLE, + "[<channel-name>]"], + ["network", cmdNetwork, CMD_CONSOLE, + "<network-name>"], + ["network-motif", cmdMotif, CMD_NEED_NET | CMD_CONSOLE, + "[<motif> [<network>]]"], + ["network-pref", cmdPref, CMD_NEED_NET | CMD_CONSOLE, + "[<pref-name> [<pref-value>]]"], + ["networks", cmdNetworks, CMD_CONSOLE], + ["nick", cmdNick, CMD_CONSOLE, + "[<nickname>]"], + ["notice", cmdNotice, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> <message>"], + ["notify", cmdNotify, CMD_NEED_SRV | CMD_CONSOLE, + "[<nickname> [<...>]]"], + ["open-at-startup", cmdOpenAtStartup, CMD_CONSOLE, + "[<toggle>]"], + ["oper", cmdOper, CMD_NEED_SRV | CMD_CONSOLE, + "<opername> [<password>]"], + ["ping", cmdPing, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname>"], + ["plugin-pref", cmdPref, CMD_CONSOLE, + "<plugin> [<pref-name> [<pref-value>]]"], + ["pref", cmdPref, CMD_CONSOLE, + "[<pref-name> [<pref-value>]]"], + ["print", cmdPrint, CMD_CONSOLE], + ["query", cmdQuery, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<message>]"], + ["quit", cmdQuit, CMD_CONSOLE, + "[<reason>]"], + ["quote", cmdQuote, CMD_NEED_NET | CMD_CONSOLE, + "<irc-command>"], + ["rename", cmdRename, CMD_CONSOLE, + "[<label>]"], + ["reload-plugin", cmdReload, CMD_CONSOLE, + "<plugin>"], + ["rlist", cmdRlist, CMD_NEED_SRV | CMD_CONSOLE, + "<regexp>"], + ["reconnect", cmdReconnect, CMD_NEED_NET | CMD_CONSOLE, + "[<reason>]"], + ["reconnect-all", cmdReconnectAll, CMD_CONSOLE, + "[<reason>]"], + ["rejoin", cmdRejoin, + CMD_NEED_SRV | CMD_NEED_CHAN | CMD_CONSOLE, + "[<reason>]"], + ["reload-ui", cmdReloadUI, 0], + ["save", cmdSave, CMD_CONSOLE, + "[<filename> [<savetype>]]"], + ["say", cmdSay, CMD_CONSOLE, + "<message>"], + ["server", cmdServer, CMD_CONSOLE, + "<hostname> [<port> [<password>]]"], + ["set-current-view", cmdSetCurrentView, 0, + "<view>"], + ["stats", cmdSimpleCommand, CMD_NEED_SRV | CMD_CONSOLE, + "[<params>]"], + ["squery", cmdSquery, CMD_NEED_SRV | CMD_CONSOLE, + "<service> [<commands>]"], + ["sslserver", cmdServer, CMD_CONSOLE, + "<hostname> [<port> [<password>]]"], + ["ssl-exception", cmdSSLException, 0, + "[<hostname> <port> [<connect>]]"], + ["stalk", cmdStalk, CMD_CONSOLE, + "[<text>]"], + ["supports", cmdSupports, CMD_NEED_SRV | CMD_CONSOLE], + ["sync-font", cmdSync, 0], + ["sync-header", cmdSync, 0], + ["sync-log", cmdSync, 0], + ["sync-motif", cmdSync, 0], + ["sync-timestamp", cmdSync, 0], + ["sync-window", cmdSync, 0], + ["testdisplay", cmdTestDisplay, CMD_CONSOLE], + ["text-direction", cmdTextDirection, 0, + "<dir>"], + ["time", cmdTime, CMD_NEED_SRV | CMD_CONSOLE, + "[<nickname>]"], + ["timestamps", cmdTimestamps, CMD_CONSOLE, + "[<toggle>]"], + ["toggle-ui", cmdToggleUI, CMD_CONSOLE, + "<thing>"], + ["toggle-pref", cmdTogglePref, 0, + "<pref-name>"], + ["toggle-group", cmdToggleGroup, 0, + "<group-id>"], + ["topic", cmdTopic, CMD_NEED_CHAN | CMD_CONSOLE, + "[<new-topic>]"], + ["unalias", cmdAlias, CMD_CONSOLE, + "<alias-name>"], + ["unignore", cmdIgnore, CMD_NEED_NET | CMD_CONSOLE, + "<mask>"], + ["unban", cmdBanOrExcept, CMD_NEED_CHAN | CMD_CONSOLE, + "<nickname>"], + ["unexcept", cmdBanOrExcept, CMD_NEED_CHAN | CMD_CONSOLE], + ["uninstall-plugin", cmdUninstallPlugin, CMD_CONSOLE, + "<plugin>"], + ["unstalk", cmdUnstalk, CMD_CONSOLE, + "<text>"], + ["urls", cmdURLs, CMD_CONSOLE, + "[<number>]"], + ["user", cmdUser, CMD_CONSOLE, + "[<username> <description>]"], + ["userhost", cmdUserhost, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<...>]"], + ["userip", cmdUserip, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<...>]"], + ["usermode", cmdUsermode, CMD_CONSOLE, + "[<new-mode>]"], + ["user-motif", cmdMotif, CMD_NEED_USER | CMD_CONSOLE, + "[<motif> [<user>]]"], + ["user-pref", cmdPref, CMD_NEED_USER | CMD_CONSOLE, + "[<pref-name> [<pref-value>]]"], + ["version", cmdVersion, CMD_NEED_SRV | CMD_CONSOLE, + "[<nickname>]"], + ["websearch", cmdWebSearch, CMD_CONSOLE, + "<selected-text>"], + ["who", cmdWho, CMD_NEED_SRV | CMD_CONSOLE, + "<rest>"], + ["whois", cmdWhoIs, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<...>]"], + ["whowas", cmdWhoWas, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<limit>]"], + ["wii", cmdWhoIsIdle, CMD_NEED_SRV | CMD_CONSOLE, + "<nickname> [<...>]"], + + /* aliases */ + ["exit", "quit", CMD_CONSOLE, + "[<reason>]"], + ["j", "join", CMD_CONSOLE, + "[<channel-name> [<key>]]"], + ["pass", "quote PASS", CMD_CONSOLE, + "<password>"], + ["part", "leave", CMD_CONSOLE], + ["raw", "quote", CMD_CONSOLE], + // Shortcuts to useful URLs: + ["faq", "goto-url-newtab faq", 0], + ["homepage", "goto-url-newtab homepage", 0], + // Used to display a nickname in the menu only. + ["label-user", "echo", 0, + "<unspecified>"], + ["label-user-multi", "echo", 0, + "<unspecified>"], + // These are all the font family/size menu commands... + ["font-family-default", "font-family default", 0], + ["font-family-serif", "font-family serif", 0], + ["font-family-sans-serif", "font-family sans-serif", 0], + ["font-family-monospace", "font-family monospace", 0], + ["font-size-default", "font-size default", 0], + ["font-size-small", "font-size small", 0], + ["font-size-medium", "font-size medium", 0], + ["font-size-large", "font-size large", 0], + ["font-size-bigger", "font-size bigger", 0], + // This next command is not visible; it maps to Ctrl-=, which is what + // you get when the user tries to do Ctrl-+ (previous command's key). + ["font-size-bigger2", "font-size bigger", 0], + ["font-size-smaller", "font-size smaller", 0], + ["toggle-oas", "open-at-startup toggle", 0], + ["toggle-ccm", "toggle-pref collapseMsgs", 0], + ["toggle-copy", "toggle-pref copyMessages", 0], + ["toggle-usort", "toggle-pref sortUsersByMode", 0], + ["toggle-umode", "toggle-pref showModeSymbols", 0], + ["toggle-timestamps","timestamps toggle", 0], + ["motif-dark", "motif dark", 0], + ["motif-light", "motif light", 0], + ["sync-output", "evalsilent syncOutputFrame(this)", 0], + ["userlist", "toggle-ui userlist", CMD_CONSOLE], + ["tabstrip", "toggle-ui tabstrip", CMD_CONSOLE], + ["statusbar", "toggle-ui status", CMD_CONSOLE], + ["header", "toggle-ui header", CMD_CONSOLE], + + // text-direction aliases + ["rtl", "text-direction rtl", CMD_CONSOLE], + ["ltr", "text-direction ltr", CMD_CONSOLE], + ["toggle-text-dir", "text-direction toggle", 0], + ["irtl", "input-text-direction rtl", CMD_CONSOLE], + ["iltr", "input-text-direction ltr", CMD_CONSOLE], + // Services aliases + ["cs", "quote cs", 0], + ["ms", "quote ms", 0], + ["ns", "quote ns", 0] + ]; + + // set the stringbundle associated with these commands. + cmdary.stringBundle = client.defaultBundle; + + client.commandManager = new CommandManager(client.defaultBundle); + client.commandManager.defaultFlags = CMD_CONSOLE; + client.commandManager.isCommandSatisfied = isCommandSatisfied; + client.commandManager.defineCommands(cmdary); + + var restList = ["reason", "action", "text", "message", "params", "font", + "expression", "ircCommand", "prefValue", "newTopic", "file", + "password", "commandList", "commands", "description", + "selectedText"]; + var stateList = ["connect"]; + + client.commandManager.argTypes.__aliasTypes__(restList, "rest"); + client.commandManager.argTypes.__aliasTypes__(stateList, "state"); + client.commandManager.argTypes["plugin"] = parsePlugin; +} + +function isCommandSatisfied(e, command) +{ + if (typeof command == "undefined") + command = e.command; + else if (typeof command == "string") + command = this.commands[command]; + + if (command.flags & CMD_NEED_USER) + { + if (!("user" in e) || !e.user) + { + e.parseError = getMsg(MSG_ERR_NEED_USER, command.name); + return false; + } + } + + if (command.flags & CMD_NEED_CHAN) + { + if (!("channel" in e) || !e.channel) + { + e.parseError = getMsg(MSG_ERR_NEED_CHANNEL, command.name); + return false; + } + } + + if (command.flags & CMD_NEED_SRV) + { + if (!("server" in e) || !e.server) + { + e.parseError = getMsg(MSG_ERR_NEED_SERVER, command.name); + return false; + } + + if (e.network.state != NET_ONLINE) + { + e.parseError = MSG_ERR_NOT_CONNECTED; + return false; + } + } + + if (command.flags & (CMD_NEED_NET | CMD_NEED_SRV | CMD_NEED_CHAN)) + { + if (!("network" in e) || !e.network) + { + e.parseError = getMsg(MSG_ERR_NEED_NETWORK, command.name); + return false; + } + } + + return CommandManager.prototype.isCommandSatisfied(e, command); +} + +CIRCChannel.prototype.dispatch = +CIRCNetwork.prototype.dispatch = +CIRCUser.prototype.dispatch = +CIRCDCCChat.prototype.dispatch = +CIRCDCCFileTransfer.prototype.dispatch = +client.dispatch = +function this_dispatch(text, e, isInteractive, flags) +{ + e = getObjectDetails(this, e); + return dispatch(text, e, isInteractive, flags); +} + +function dispatch(text, e, isInteractive, flags) +{ + if (typeof isInteractive == "undefined") + isInteractive = false; + + if (!e) + e = new Object(); + + if (!("sourceObject" in e)) + e.__proto__ = getObjectDetails(client.currentObject); + + if (!("isInteractive" in e)) + e.isInteractive = isInteractive; + + if (!("inputData" in e)) + e.inputData = ""; + + /* split command from arguments */ + var ary = text.match(/(\S+) ?(.*)/); + if (!ary) + { + display(getMsg(MSG_ERR_UNKNOWN_COMMAND, "")); + return null; + } + + e.commandText = ary[1]; + if (ary[2]) + e.inputData = stringTrim(ary[2]); + + /* list matching commands */ + ary = client.commandManager.list(e.commandText, flags, true); + var rv = null; + var i; + + switch (ary.length) + { + case 0: + /* no match, try again */ + if (e.server && e.server.isConnected && + client.prefs["guessCommands"]) + { + /* Want to keep the source details. */ + var e2 = getObjectDetails(e.sourceObject); + e2.inputData = e.commandText + " " + e.inputData; + return dispatch("quote", e2); + } + + display(getMsg(MSG_ERR_UNKNOWN_COMMAND, e.commandText), MT_ERROR); + break; + + case 1: + /* one match, good for you */ + var cm = client.commandManager; + + if (cm.currentDispatchDepth >= cm.maxDispatchDepth) + { + /* We've reatched the max dispatch depth, so we need to unwind + * the entire stack of commands. + */ + cm.dispatchUnwinding = true; + } + // Don't start any new commands while unwinding. + if (cm.dispatchUnwinding) + break; + + cm.currentDispatchDepth++; + + var ex; + try + { + rv = dispatchCommand(ary[0], e, flags); + } + catch (ex) + { + display(getMsg(MSG_ERR_INTERNAL_DISPATCH, ary[0].name), + MT_ERROR); + display(formatException(ex), MT_ERROR); + if (typeof ex == "object" && "stack" in ex) + dd(formatException(ex) + "\n" + ex.stack); + else + dd(formatException(ex), MT_ERROR); + } + + cm.currentDispatchDepth--; + if (cm.dispatchUnwinding && (cm.currentDispatchDepth == 0)) + { + /* Last level to unwind, and this is where we display the + * message. We need to leave it until here because displaying + * a message invokes a couple of commands itself, and we need + * to not be right on the dispatch limit for that. + */ + cm.dispatchUnwinding = false; + display(getMsg(MSG_ERR_MAX_DISPATCH_DEPTH, ary[0].name), + MT_ERROR); + } + break; + + default: + /* more than one match, show the list */ + var str = ""; + for (i in ary) + str += (str) ? ", " + ary[i].name : ary[i].name; + display(getMsg(MSG_ERR_AMBIGCOMMAND, + [e.commandText, ary.length, str]), MT_ERROR); + } + + return rv; +} + +function dispatchCommand (command, e, flags) +{ + function displayUsageError (e, details) + { + if (!("isInteractive" in e) || !e.isInteractive) + { + var caller = Components.stack.caller.caller; + if (caller.name == "dispatch") + caller = caller.caller; + var error = new Error (details); + error.fileName = caller.filename; + error.lineNumber = caller.lineNumber; + error.name = caller.name; + display (formatException(error), MT_ERROR); + } + else + { + display (details, MT_ERROR); + } + + //display (getMsg(MSG_FMT_USAGE, [e.command.name, e.command.usage]), + // MT_USAGE); + return null; + }; + + function callHooks (command, isBefore) + { + var names, hooks; + + if (isBefore) + hooks = command.beforeHooks; + else + hooks = command.afterHooks; + + for (var h in hooks) + { + if ("dbgDispatch" in client && client.dbgDispatch) + { + dd ("calling " + (isBefore ? "before" : "after") + + " hook " + h); + } + try + { + hooks[h](e); + } + catch (ex) + { + if (e.command.name != "hook-session-display") + { + display(getMsg(MSG_ERR_INTERNAL_HOOK, h), MT_ERROR); + display(formatException(ex), MT_ERROR); + } + else + { + dd(getMsg(MSG_ERR_INTERNAL_HOOK, h)); + } + + dd("Caught exception calling " + + (isBefore ? "before" : "after") + " hook " + h); + dd(formatException(ex)); + if (typeof ex == "object" && "stack" in ex) + dd(ex.stack); + else + dd(getStackTrace()); + } + } + }; + + e.command = command; + + if (!e.command.enabled) + { + /* disabled command */ + display (getMsg(MSG_ERR_DISABLED, e.command.name), + MT_ERROR); + return null; + } + + function parseAlias(aliasLine, e) { + /* Only 1 of these will be presented to the user. Math.max is used to + supply the 'worst' error */ + const ALIAS_ERR_REQ_PRMS = 1; + const ALIAS_ERR_REQ_SRV = 2; + const ALIAS_ERR_REQ_RECIP = 3; + + /* double slashes because of the string to regexp conversion, which + turns these into single slashes */ + const SIMPLE_REPLACE = "\\$\\((\\d+)\\)"; + const CUMUL_REPLACE = "\\$\\((\\d+)\\+\\)"; + const RANGE_REPLACE = "\\$\\((\\d+)\\-(\\d+)\\)"; + const NICK_REPLACE = "\\$\\((nick)\\)"; + const RECIP_REPLACE = "\\$\\((recip)\\)"; + const ALL_REPLACE = "\\$\\((all)\\)"; + if (!aliasLine.match(/\$/)) + { + if (e.inputData) + display(getMsg(MSG_EXTRA_PARAMS, e.inputData), MT_WARN); + return aliasLine; + } + + function replaceAll(match, single, cumulative, start, end, nick, recip, all) + { + if (single) + { + // Simple 1-parameter replace + if (arrayHasElementAt(parameters, single - 1)) + { + paramsUsed = Math.max(paramsUsed, single); + return parameters[single-1]; + } + maxParamsAsked = Math.max(maxParamsAsked, single); + errorMsg = Math.max(ALIAS_ERR_REQ_PRMS, errorMsg); + return match; + } + if (cumulative) + { + // Cumulative Replace: parameters cumulative and up + if (arrayHasElementAt(parameters, cumulative - 1)) + { + paramsUsed = parameters.length; + // there are never leftover parameters for $(somenumber+) + return parameters.slice(cumulative - 1).join(" "); + } + maxParamsAsked = Math.max(maxParamsAsked, cumulative); + errorMsg = Math.max(ALIAS_ERR_REQ_PRMS, errorMsg); + return match; + } + if (start && end) + { + // Ranged replace: parameters start through end + //'decrement to correct 0-based index. + if (start > end) + { + var iTemp = end; + end = start; + start = iTemp; + // We obviously have a very stupid user, but we're nice + } + start--; + if (arrayHasElementAt(parameters, start) && + arrayHasElementAt(parameters, end - 1)) + { + paramsUsed = Math.max(paramsUsed,end); + return parameters.slice(start, end).join(" "); + } + maxParamsAsked = Math.max(maxParamsAsked, end); + errorMsg = Math.max(ALIAS_ERR_REQ_PRMS, errorMsg); + return match; + } + if (nick) + { + // Replace with own nickname + if (e.network && e.server && e.network.state == NET_ONLINE) + return e.server.me.unicodeName; + + errorMsg = Math.max(ALIAS_ERR_REQ_SRV, errorMsg); + return null; + } + if (recip) + { + // Replace with current recipient + if (e.channel) + return e.channel.unicodeName; + + if (e.user) + return e.user.unicodeName; + + errorMsg = ALIAS_ERR_REQ_RECIP; + return null; + } + // Replace with all parameters + paramsUsed = parameters.length; + return parameters.join(" "); + }; + + // If the replace function has a problem, this is an error constant: + var errorMsg = 0; + + var paramsUsed = 0; + var maxParamsAsked = 0; + + /* set parameters array and escaping \ and ; in parameters so the + * parameters don't get split up by the command list split later on */ + e.inputData = e.inputData.replace(/([\\;])/g, "\\$1"); + var parameters = e.inputData.match(/\S+/g); + if (!parameters) + parameters = []; + + // replace in the command line. + var expr = [SIMPLE_REPLACE, CUMUL_REPLACE, RANGE_REPLACE, NICK_REPLACE, + RECIP_REPLACE, ALL_REPLACE].join("|"); + aliasLine = aliasLine.replace(new RegExp(expr, "gi"), replaceAll); + + if (errorMsg) + { + switch (errorMsg) + { + case ALIAS_ERR_REQ_PRMS: + display(getMsg(MSG_ERR_REQUIRED_NR_PARAM, + [maxParamsAsked - parameters.length, + maxParamsAsked]), MT_ERROR); + break; + case ALIAS_ERR_REQ_SRV: + display(getMsg(MSG_ERR_NEED_SERVER, e.command.name), + MT_ERROR); + break; + case ALIAS_ERR_REQ_RECIP: + display(getMsg(MSG_ERR_NEED_RECIP, e.command.name), + MT_ERROR); + break; + } + return null; + } + + // return the revised command line. + if (paramsUsed < parameters.length) + { + var pmstring = parameters.slice(paramsUsed, + parameters.length).join(" "); + display(getMsg(MSG_EXTRA_PARAMS, pmstring), MT_WARN); + } + return aliasLine; + }; + + function callBeforeHooks() + { + if ("beforeHooks" in client.commandManager) + callHooks(client.commandManager, true); + if ("beforeHooks" in e.command) + callHooks(e.command, true); + }; + + function callAfterHooks() + { + if ("afterHooks" in e.command) + callHooks(e.command, false); + if ("afterHooks" in client.commandManager) + callHooks(client.commandManager, false); + }; + + var h, i; + + if (typeof e.command.func == "function") + { + /* dispatch a real function */ + + client.commandManager.parseArguments(e); + if ("parseError" in e) + return displayUsageError(e, e.parseError); + + if (("dbgDispatch" in client) && client.dbgDispatch) + { + var str = ""; + for (i = 0; i < e.command.argNames.length; ++i) + { + var name = e.command.argNames[i]; + if (name in e) + str += " " + name + ": " + e[name]; + else if (name != ":") + str += " ?" + name; + } + dd(">>> " + e.command.name + str + " <<<"); + } + + callBeforeHooks(); + try + { + e.returnValue = e.command.func(e); + } + finally + { + callAfterHooks(); + /* set client.lastEvent *after* dispatching, so the dispatched + * function actually get's a chance to see the last event. */ + if (("dbgDispatch" in client) && client.dbgDispatch) + client.lastEvent = e; + } + } + else if (typeof e.command.func == "string") + { + /* dispatch an alias (semicolon delimited list of subcommands) */ + + var commandList; + //Don't make use of e.inputData if we have multiple commands in 1 alias + if (e.command.func.match(/\$\(.*\)|(?:^|[^\\])(?:\\\\)*;/)) + commandList = parseAlias(e.command.func, e); + else + commandList = e.command.func + " " + e.inputData; + + if (commandList == null) + return null; + commandList = commandList.split(";"); + + i = 0; + while (i < commandList.length) { + if (commandList[i].match(/(?:^|[^\\])(?:\\\\)*$/) || + (i == commandList.length - 1)) + { + commandList[i] = commandList[i].replace(/\\(.)/g, "$1"); + i++; + } + else + { + commandList[i] = commandList[i] + ";" + commandList[i + 1]; + commandList.splice(i + 1, 1); + } + } + + callBeforeHooks(); + try + { + for (i = 0; i < commandList.length; ++i) + { + var newEvent = Clone(e); + delete newEvent.command; + commandList[i] = stringTrim(commandList[i]); + dispatch(commandList[i], newEvent, flags); + } + } + finally + { + callAfterHooks(); + } + } + else + { + display(getMsg(MSG_ERR_NOTIMPLEMENTED, e.command.name), MT_ERROR); + return null; + } + + return ("returnValue" in e) ? e.returnValue : null; +} + +/* parse function for <plugin> parameters */ +function parsePlugin(e, name) +{ + var ary = e.unparsedData.match(/(?:(\S+))(?:\s+(.*))?$/); + if (!ary) + return false; + + var plugin; + + if (ary[1]) + { + plugin = getPluginById(ary[1]); + if (!plugin) + return false; + + } + + e.unparsedData = ary[2] || ""; + e[name] = plugin; + return true; +} + +function getToggle (toggle, currentState) +{ + if (toggle == "toggle") + toggle = !currentState; + + return toggle; +} + +/****************************************************************************** + * command definitions from here on down. + */ + +function cmdDisablePlugin(e) +{ + disablePlugin(e.plugin, false); +} + +function cmdEnablePlugin(e) +{ + if (e.plugin.enabled) + { + display(getMsg(MSG_IS_ENABLED, e.plugin.id)); + return; + } + + if (e.plugin.API > 0) + { + if (!e.plugin.enable()) + { + display(getMsg(MSG_CANT_ENABLE, e.plugin.id)); + e.plugin.prefs["enabled"] = false; + return; + } + e.plugin.prefs["enabled"] = true; + } + else if (!("enablePlugin" in e.plugin.scope)) + { + display(getMsg(MSG_CANT_ENABLE, e.plugin.id)); + return; + } + else + { + e.plugin.scope.enablePlugin(); + } + + display(getMsg(MSG_PLUGIN_ENABLED, e.plugin.id)); + e.plugin.enabled = true; +} + +function cmdBanOrExcept(e) +{ + var modestr; + switch (e.command.name) + { + case "ban": + modestr = "+bbbb"; + break; + + case "unban": + modestr = "-bbbb"; + break; + + case "except": + modestr = "+eeee"; + break; + + case "unexcept": + modestr = "-eeee"; + break; + + default: + ASSERT(0, "Dispatch from unknown name " + e.command.name); + return; + } + + /* If we're unbanning, or banning in odd cases, we may actually be talking + * about a user who is not in the channel, so we need to check the server + * for information as well. + */ + if (!e.user && e.nickname) + e.user = e.channel.getUser(e.nickname); + if (!e.user && e.nickname) + e.user = e.server.getUser(e.nickname); + + var masks = new Array(); + + if (e.userList) + { + for (var i = 0; i < e.userList.length; i++) + masks.push(fromUnicode(e.userList[i].getBanMask(), e.server)); + } + else if (e.user) + { + // We have a real user object, so get their proper 'ban mask'. + masks = [fromUnicode(e.user.getBanMask(), e.server)]; + } + else if (e.nickname) + { + /* If we have either ! or @ in the nickname assume the user has given + * us a complete mask and pass it directly, otherwise assume it is + * only the nickname and use * for username/host. + */ + masks = [fromUnicode(e.nickname, e.server)]; + if (!/[!@]/.test(e.nickname)) + masks[0] = masks[0] + "!*@*"; + } + else + { + // Nothing specified, so we want to list the bans/excepts. + masks = [""]; + } + + // Collapses into groups we can do individually. + masks = combineNicks(masks); + + for (var i = 0; i < masks.length; i++) + { + e.server.sendData("MODE " + e.channel.encodedName + " " + + modestr.substr(0, masks[i].count + 1) + + " " + masks[i] + "\n"); + } +} + +function cmdCancel(e) +{ + if (e.network && e.network.isRunningList()) + { + // We're running a /list, terminate the output so we return to sanity. + display(MSG_CANCELLING_LIST); + return e.network.abortList(); + } + + if (e.network && ((e.network.state == NET_CONNECTING) || + (e.network.state == NET_WAITING))) + { + // We're trying to connect to a network, and want to cancel. Do so: + if (e.deleteWhenDone) + e.network.deleteWhenDone = true; + + display(getMsg(MSG_CANCELLING, e.network.unicodeName)); + return e.network.cancel(); + } + + // If we're transferring a file, abort it. + var source = e.sourceObject; + if ((source.TYPE == "IRCDCCFileTransfer") && source.isActive()) + return source.abort(); + + display(MSG_NOTHING_TO_CANCEL, MT_ERROR); +} + +function cmdChanUserMode(e) +{ + var modestr; + switch (e.command.name) + { + case "op": + modestr = "+oooo"; + break; + + case "deop": + modestr = "-oooo"; + break; + + case "hop": + modestr = "+hhhh"; + break; + + case "dehop": + modestr = "-hhhh"; + break; + + case "voice": + modestr = "+vvvv"; + break; + + case "devoice": + modestr = "-vvvv"; + break; + + default: + ASSERT(0, "Dispatch from unknown name " + e.command.name); + return; + } + + var nicks; + var user; + var nickList = new Array(); + // Prefer pre-canonicalised list, then a * passed to the command directly, + // then a normal list, then finally a singular item (canon. or otherwise). + if (e.canonNickList) + { + nicks = combineNicks(e.canonNickList); + } + else if (e.nickname && (e.nickname == "*")) + { + var me = e.server.me; + var mode = modestr.substr(1, 1); + var adding = modestr[0] == "+"; + for (userKey in e.channel.users) + { + var user = e.channel.users[userKey]; + /* Never change our own mode and avoid trying to change someone + * else in a no-op manner (e.g. voicing an already voiced user). + */ + if ((user.encodedName != me.encodedName) && + (arrayContains(user.modes, mode) ^ adding)) + { + nickList.push(user.encodedName); + } + } + nicks = combineNicks(nickList); + } + else if (e.nicknameList) + { + for (var i = 0; i < e.nicknameList.length; i++) + { + user = e.channel.getUser(e.nicknameList[i]); + if (!user) + { + display(getMsg(MSG_ERR_UNKNOWN_USER, e.nicknameList[i]), MT_ERROR); + return; + } + nickList.push(user.encodedName); + } + nicks = combineNicks(nickList); + } + else if (e.nickname) + { + user = e.channel.getUser(e.nickname); + if (!user) + { + display(getMsg(MSG_ERR_UNKNOWN_USER, e.nickname), MT_ERROR); + return; + } + var str = new String(user.encodedName); + str.count = 1; + nicks = [str]; + } + else + { + // Panic? + dd("Help! Channel user mode command with no users...?"); + } + + for (var i = 0; i < nicks.length; ++i) + { + e.server.sendData("MODE " + e.channel.encodedName + " " + + modestr.substr(0, nicks[i].count + 1) + + " " + nicks[i] + "\n"); + } +} + +function cmdCharset(e) +{ + var pm; + + if (e.command.name == "default-charset") + { + pm = client.prefManager; + msg = MSG_CURRENT_CHARSET; + } + else + { + pm = e.sourceObject.prefManager; + msg = MSG_CURRENT_CHARSET_VIEW; + } + + if (e.newCharset) + { + if (e.newCharset == "-") + { + pm.clearPref("charset"); + } + else + { + if(!checkCharset(e.newCharset)) + { + display(getMsg(MSG_ERR_INVALID_CHARSET, e.newCharset), + MT_ERROR); + return; + } + pm.prefs["charset"] = e.newCharset; + } + } + + display(getMsg(msg, pm.prefs["charset"])); + + // If we're on a channel, get the topic again so it can be re-decoded. + if (e.newCharset && e.server && e.channel) + e.server.sendData("TOPIC " + e.channel.encodedName + "\n"); +} + +function cmdCreateTabForView(e) +{ + return getTabForObject(e.view, true); +} + +function cmdDelayed(e) +{ + function _dispatch() + { + // Clear inputData so that commands without arguments work properly + e.inputData = ""; + dispatch(e.rest, e, e.isInteractive); + } + setTimeout(_dispatch, e.delay * 1000); +} + +function cmdSync(e) +{ + var fun; + + switch (e.command.name) + { + case "sync-font": + fun = function () + { + if (view.prefs["displayHeader"]) + view.setHeaderState(false); + view.changeCSS(view.getFontCSS("data"), "cz-fonts"); + if (view.prefs["displayHeader"]) + view.setHeaderState(true); + }; + break; + + case "sync-header": + fun = function () + { + view.setHeaderState(view.prefs["displayHeader"]); + }; + break; + + case "sync-motif": + fun = function () + { + view.changeCSS(view.prefs["motif.current"]); + updateAppMotif(view.prefs["motif.current"]); + // Refresh the motif settings. + view.updateMotifSettings(); + }; + break; + + case "sync-timestamp": + fun = function () + { + updateTimestamps(view); + }; + break; + + case "sync-window": + fun = function () + { + if (window && window.location && + window.location.href != view.prefs["outputWindowURL"]) + { + syncOutputFrame(view); + } + }; + break; + + case "sync-log": + fun = function () + { + if (view.prefs["log"] ^ Boolean(view.logFile)) + { + if (view.prefs["log"]) + client.openLogFile(view, true); + else + client.closeLogFile(view, true); + updateLoggingIcon(); + } + }; + break; + } + + var view = e.sourceObject; + var window; + if (("frame" in view) && view.frame) + window = getContentWindow(view.frame); + + try + { + fun(); + } + catch(ex) + { + dd("Exception in " + e.command.name + " for " + e.sourceObject.unicodeName + ": " + ex); + } +} + +function cmdSimpleCommand(e) +{ + e.server.sendData(e.command.name + " " + e.inputData + "\n"); +} + +function cmdSquery(e) +{ + var data; + + if (e.commands) + data = "SQUERY " + e.service + " :" + e.commands + "\n"; + else + data = "SQUERY " + e.service + "\n"; + + e.server.sendData(data); +} + +function cmdHelp(e) +{ + if (!e.pattern) + { + if ("hello" in e) + display(MSG_HELP_INTRO, "HELLO"); + else + display(MSG_HELP_INTRO); + return; + } + + var ary = client.commandManager.list(e.pattern, CMD_CONSOLE, true); + + if (ary.length == 0) + { + display(getMsg(MSG_ERR_UNKNOWN_COMMAND, e.pattern), MT_ERROR); + return; + } + + for (var i in ary) + { + display(getMsg(MSG_FMT_USAGE, [ary[i].name, ary[i].helpUsage]), + MT_USAGE); + display(ary[i].help, MT_HELP); + } + + return; +} + +function cmdTestDisplay(e) +{ + startMsgGroup("testdisplay", MSG_COLLAPSE_TEST); + display(MSG_TEST_HELLO, MT_HELLO); + display(MSG_TEST_INFO, MT_INFO); + display(MSG_TEST_ERROR, MT_ERROR); + display(MSG_TEST_HELP, MT_HELP); + display(MSG_TEST_USAGE, MT_USAGE); + display(MSG_TEST_STATUS, MT_STATUS); + + if (e.server && e.server.me) + { + var me = e.server.me; + var sampleUser = {TYPE: "IRCUser", + encodedName: "ircmonkey", collectionKey: ":ircmonkey", + unicodeName: "IRCMonkey", viewName: "IRCMonkey", + host: "", name: "IRCMonkey"}; + var sampleChannel = {TYPE: "IRCChannel", + encodedName: "#mojo", collectionKey: ":#mojo", + unicodeName: "#Mojo", viewName: "#Mojo", + name: "#Mojo"}; + + function test (from, to) + { + var fromText = (from != me) ? from.TYPE + " ``" + from.name + "''" : + MSG_YOU; + var toText = (to != me) ? to.TYPE + " ``" + to.name + "''" : + MSG_YOU; + + display (getMsg(MSG_TEST_PRIVMSG, [fromText, toText]), + "PRIVMSG", from, to); + display (getMsg(MSG_TEST_ACTION, [fromText, toText]), + "ACTION", from, to); + display (getMsg(MSG_TEST_NOTICE, [fromText, toText]), + "NOTICE", from, to); + } + + test (sampleUser, me); /* from user to me */ + test (me, sampleUser); /* me to user */ + + display(MSG_TEST_URL, "PRIVMSG", sampleUser, me); + display(MSG_TEST_STYLES, "PRIVMSG", sampleUser, me); + display(MSG_TEST_EMOTICON, "PRIVMSG", sampleUser, me); + display(MSG_TEST_RHEET, "PRIVMSG", sampleUser, me); + display(unescape(MSG_TEST_CTLCHR), "PRIVMSG", sampleUser, me); + display(unescape(MSG_TEST_COLOR), "PRIVMSG", sampleUser, me); + display(MSG_TEST_QUOTE, "PRIVMSG", sampleUser, me); + + if (e.channel) + { + test (sampleUser, sampleChannel); /* user to channel */ + test (me, sampleChannel); /* me to channel */ + display(MSG_TEST_TOPIC, "TOPIC", sampleUser, sampleChannel); + display(MSG_TEST_JOIN, "JOIN", sampleUser, sampleChannel); + display(MSG_TEST_PART, "PART", sampleUser, sampleChannel); + display(MSG_TEST_KICK, "KICK", sampleUser, sampleChannel); + display(MSG_TEST_QUIT, "QUIT", sampleUser, sampleChannel); + display(getMsg(MSG_TEST_STALK, me.unicodeName), + "PRIVMSG", sampleUser, sampleChannel); + display(MSG_TEST_STYLES, "PRIVMSG", me, sampleChannel); + } + } + endMsgGroup(); +} + +function cmdNetwork(e) +{ + let network = client.getNetwork(e.networkName); + + if (!network) + { + display (getMsg(MSG_ERR_UNKNOWN_NETWORK, e.networkName), MT_ERROR); + return; + } + + dispatch("create-tab-for-view", { view: network }); + dispatch("set-current-view", { view: network }); +} + +function cmdNetworks(e) +{ + var wrapper = newInlineText(MSG_NETWORKS_HEADA); + + var netnames = keys(client.networks).sort(); + + for (let i = 0; i < netnames.length; i++) + { + let net = client.networks[netnames[i]]; + let hasSecure = networkHasSecure(net.serverList); + + var linkData = { + "data": net.unicodeName, + "href": (hasSecure ? "ircs://" : "irc://") + net.canonicalName + }; + wrapper.appendChild(newInlineText(linkData, "chatzilla-link", "a")); + + if (i < netnames.length - 1) + wrapper.appendChild(document.createTextNode(", ")); + } + + // Display an "Edit" link. + var spanb = document.createElementNS(XHTML_NS, "html:span"); + + client.munger.getRule(".inline-buttons").enabled = true; + var msg = getMsg(MSG_NETWORKS_HEADB2, "edit-networks"); + client.munger.munge(msg, spanb, getObjectDetails(client.currentObject)); + client.munger.getRule(".inline-buttons").enabled = false; + + wrapper.appendChild(spanb); + display(wrapper, MT_INFO); +} + +function cmdEditNetworks(e) +{ + toOpenWindowByType("irc:chatzilla:networks", + "chrome://chatzilla/content/networks-edit.xul", + "chrome,resizable,dialog", client); +} + +function cmdServer(e) +{ + let scheme = (e.command.name == "sslserver") ? "ircs" : "irc"; + + var ary = e.hostname.match(/^(.*):(\d+)$/); + if (ary) + { + // Foolish user obviously hasn't read the instructions, but we're nice. + e.password = e.port; + e.port = ary[2]; + e.hostname = ary[1]; + } + + gotoIRCURL({scheme: scheme, host: e.hostname, port: e.port, + pass: e.password, isserver: true}); +} + +function cmdSSLException(e) +{ + var opts = "chrome,centerscreen,modal"; + var location = e.hostname ? e.hostname + ':' + e.port : undefined; + var args = {location: location, prefetchCert: true}; + + window.openDialog("chrome://pippki/content/exceptionDialog.xul", + "", opts, args); + + if (!args.exceptionAdded) + return; + + if (e.connect) + { + // When we come via the inline button, we just want to reconnect + if (e.source == "mouse") + dispatch("reconnect"); + else + dispatch("sslserver " + e.hostname + " " + e.port); + } +} + + +function cmdQuit(e) +{ + // if we're not connected to anything, just close the window + if (!("getConnectionCount" in client) || (client.getConnectionCount() == 0)) + { + client.userClose = true; + window.close(); + return; + } + + // Otherwise, try to close gracefully: + client.wantToQuit(e.reason, true); +} + +function cmdDisconnect(e) +{ + if ((typeof e.reason != "string") || !e.reason) + e.reason = e.network.prefs["defaultQuitMsg"]; + if (!e.reason) + e.reason = client.userAgent; + + e.network.quit(e.reason); +} + +function cmdDisconnectAll(e) +{ + var netReason; + if (confirmEx(MSG_CONFIRM_DISCONNECT_ALL, ["!yes", "!no"]) != 0) + return; + + var conNetworks = client.getConnectedNetworks(); + if (conNetworks.length <= 0) + { + display(MSG_NO_CONNECTED_NETS, MT_ERROR); + return; + } + + for (var i = 0; i < conNetworks.length; i++) + { + netReason = e.reason; + if ((typeof netReason != "string") || !netReason) + netReason = conNetworks[i].prefs["defaultQuitMsg"]; + netReason = (netReason ? netReason : client.userAgent); + conNetworks[i].quit(netReason); + } +} + +function cmdDeleteView(e) +{ + if (!e.view) + e.view = e.sourceObject; + + if (("lockView" in e.view) && e.view.lockView) + { + setTabState(e.view, "attention"); + return; + } + + if (e.view.TYPE == "IRCChannel" && e.view.joined) + { + e.view.dispatch("part", { deleteWhenDone: true }); + return; + } + + if (e.view.TYPE.substr(0, 6) == "IRCDCC") + { + if (e.view.isActive()) + e.view.abort(); + // abort() calls disconnect() if it is appropriate. + // Fall through: we don't delete on disconnect. + } + + if (e.view.TYPE == "IRCNetwork" && (e.view.state == NET_CONNECTING || + e.view.state == NET_WAITING)) + { + e.view.dispatch("cancel", { deleteWhenDone: true }); + return; + } + + if (client.viewsArray.length < 2) + { + display(MSG_ERR_LAST_VIEW, MT_ERROR); + return; + } + + var tb = getTabForObject(e.view); + if (tb) + { + var i = deleteTab (tb); + if (i != -1) + { + if (e.view.logFile) + { + e.view.logFile.close(); + e.view.logFile = null; + } + delete e.view.messageCount; + delete e.view.messages; + deleteFrame(e.view); + + var oldView = client.currentObject; + if (client.currentObject == e.view) + { + if (i >= client.viewsArray.length) + i = client.viewsArray.length - 1; + oldView = client.viewsArray[i].source + } + client.currentObject = null; + oldView.dispatch("set-current-view", { view: oldView }); + } + } +} + +function cmdHideView(e) +{ + if (!e.view) + e.view = e.sourceObject; + + if (client.viewsArray.length < 2) + { + display(MSG_ERR_LAST_VIEW_HIDE, MT_ERROR); + return; + } + + if ("messages" in e.view) + { + // Detach messages from output window content. + if (e.view.messages.parentNode) + e.view.messages.parentNode.removeChild(e.view.messages); + + /* XXX Bug 335998: Adopt the messages into our own internal document + * so that when the real one the messages were in gets incorrectly + * GC-collected (see bug) the nodes still have an ownerDocument. + */ + client.adoptNode(e.view.messages, client.hiddenDocument); + } + + var tb = getTabForObject(e.view); + + if (tb) + { + var i = deleteTab (tb); + if (i != -1) + { + deleteFrame(e.view); + + var oldView = client.currentObject; + if (client.currentObject == e.view) + { + if (i >= client.viewsArray.length) + i = client.viewsArray.length - 1; + oldView = client.viewsArray[i].source + } + client.currentObject = null; + oldView.dispatch("set-current-view", { view: oldView }); + } + } +} + +function cmdClearView(e) +{ + if (!e.view) + e.view = e.sourceObject; + + e.view.messages = null; + e.view.messageCount = 0; + + e.view.displayHere(MSG_MESSAGES_CLEARED); + + syncOutputFrame(e.view); +} + +function cmdDesc(e) +{ + if (e.network != null) // somewhere on a network + { + dispatch("network-pref", {prefValue: e.description, prefName: "desc", + network: e.network, + isInteractive: e.isInteractive}); + } + else // no network, change the general pref + { + dispatch("pref", {prefName: "desc", prefValue: e.description, + isInteractive: e.isInteractive}); + } +} + +function cmdName(e) +{ + if (e.network != null) // somewhere on a network + { + dispatch("network-pref", {prefName: "username", prefValue: e.username, + network: e.network, + isInteractive: e.isInteractive}); + } + else // no network, change the general pref + { + dispatch("pref", {prefName: "username", prefValue: e.username, + isInteractive: e.isInteractive}); + } +} + +function cmdNames(e) +{ + if (e.hasOwnProperty("channelName")) + { + e.channel = new CIRCChannel(e.server, e.channelName); + } + else + { + if (!e.channel) + { + display(getMsg(MSG_ERR_REQUIRED_PARAM, "channel-name"), MT_ERROR); + return; + } + } + + e.channel.pendingNamesReply = true; + e.server.sendData("NAMES " + e.channel.encodedName + "\n"); +} + +function cmdReconnect(e) +{ + if (e.network.isConnected()) + { + // Set reconnect flag + e.network.reconnect = true; + if (typeof e.reason != "string") + e.reason = MSG_RECONNECTING; + // Now we disconnect. + e.network.quit(e.reason); + } + else + { + e.network.connect(e.network.requireSecurity); + } +} + +function cmdReconnectAll(e) +{ + var reconnected = false; + for (var net in client.networks) + { + if (client.networks[net].isConnected() || + ("messages" in client.networks[net])) + { + client.networks[net].dispatch("reconnect", { reason: e.reason }); + reconnected = true; + } + } + if (!reconnected) + display(MSG_NO_RECONNECTABLE_NETS, MT_ERROR); +} + +function cmdRejoin(e) +{ + if (e.channel.joined) + { + if (!e.reason) + e.reason = ""; + e.channel.dispatch("part", { reason: e.reason, deleteWhenDone: false }); + } + + e.channel.join(e.channel.mode.key); +} + +function cmdRename(e) +{ + var tab = getTabForObject(e.sourceObject); + if (!tab) + { + feedback(e, getMsg(MSG_ERR_INTERNAL_DISPATCH, "rename")); + return; + } + var label = e.label || prompt(MSG_TAB_NAME_PROMPT, tab.label); + if (!label) + { + return; + } + e.sourceObject.prefs["tabLabel"] = label; +} + + +function cmdTogglePref (e) +{ + var state = !client.prefs[e.prefName]; + client.prefs[e.prefName] = state; + feedback(e, getMsg (MSG_FMT_PREF, + [e.prefName, state ? MSG_VAL_ON : MSG_VAL_OFF])); +} + +function cmdToggleGroup(e) +{ + var document = getContentDocument(e.sourceObject.frame); + var msgs = document.querySelectorAll("[msg-groups*=\"" + e.groupId + "\"]"); + if (!msgs.length) + return; + + var isHidden = (msgs[0].style.display == "none"); + for (i = 0; i < msgs.length; i++) + { + if (isHidden) + msgs[i].style.display = ""; + else + msgs[i].style.display = "none"; + } + + var els = msgs[0].previousSibling.querySelectorAll(".chatzilla-link"); + var button = els[els.length - 1]; + if (button.text == MSG_COLLAPSE_HIDE) + { + button.text = MSG_COLLAPSE_SHOW; + button.title = MSG_COLLAPSE_SHOWTITLE; + } + else + { + button.text = MSG_COLLAPSE_HIDE; + button.title = MSG_COLLAPSE_HIDETITLE; + } +} + +function cmdToggleUI(e) +{ + var ids = new Array(); + + switch (e.thing) + { + case "tabstrip": + ids = ["view-tabs"]; + break; + + case "userlist": + ids = ["main-splitter", "user-list-box"]; + break; + + case "header": + client.currentObject.prefs["displayHeader"] = + !client.currentObject.prefs["displayHeader"]; + return; + + case "status": + ids = ["status-bar"]; + break; + + default: + ASSERT (0,"Unknown element ``" + e.thing + + "'' passed to onToggleVisibility."); + return; + } + + var newState; + var elem = document.getElementById(ids[0]); + var sourceObject = e.sourceObject; + var newState = !elem.collapsed; + + for (var i in ids) + { + elem = document.getElementById(ids[i]); + elem.collapsed = newState; + } + + updateTitle(); + dispatch("focus-input"); +} + +function cmdCommands(e) +{ + display(MSG_COMMANDS_HEADER); + + var matchResult = client.commandManager.listNames(e.pattern, CMD_CONSOLE); + matchResult = matchResult.join(", "); + + if (e.pattern) + display(getMsg(MSG_MATCHING_COMMANDS, [e.pattern, matchResult])); + else + display(getMsg(MSG_ALL_COMMANDS, matchResult)); +} + +function cmdAttach(e) +{ + if (e.ircUrl.search(/ircs?:\/\//i) != 0) + e.ircUrl = "irc://" + e.ircUrl; + + var parsedURL = parseIRCURL(e.ircUrl); + if (!parsedURL) + { + display(getMsg(MSG_ERR_BAD_IRCURL, e.ircUrl), MT_ERROR); + return; + } + + gotoIRCURL(e.ircUrl); +} + +function cmdMatchUsers(e) +{ + var matches = e.channel.findUsers(e.mask); + var uc = matches.unchecked; + var msgNotChecked = ""; + + // Get a pretty list of nicknames: + var nicknames = []; + for (var i = 0; i < matches.users.length; i++) + nicknames.push(matches.users[i].unicodeName); + + var nicknameStr = arraySpeak(nicknames); + + // Were we unable to check one or more of the users? + if (uc != 0) + msgNotChecked = getMsg(MSG_MATCH_UNCHECKED, uc); + + if (matches.users.length == 0) + display(getMsg(MSG_NO_MATCHING_NICKS, msgNotChecked)); + else + display(getMsg(MSG_MATCHING_NICKS, [nicknameStr, msgNotChecked])); +} + +function cmdMe(e) +{ + if (!("act" in e.sourceObject)) + { + display(getMsg(MSG_ERR_IMPROPER_VIEW, "me"), MT_ERROR); + return; + } + _sendMsgTo(e.action, "ACTION", e.sourceObject); +} + +function cmdDescribe(e) +{ + var target = e.server.addTarget(e.target); + _sendMsgTo(e.action, "ACTION", target, e.sourceObject); +} + +function cmdMode(e) +{ + var chan; + + // Make sure the user can leave the channel name out from a channel view. + if ((!e.target || /^[\+\-].+/.test(e.target)) && + !(chan && e.server.getChannel(chan))) + { + if (e.channel) + { + chan = e.channel.canonicalName; + if (e.param && e.modestr) + { + e.paramList.unshift(e.modestr); + } + else if (e.modestr) + { + e.paramList = [e.modestr]; + e.param = e.modestr; + } + e.modestr = e.target; + } + else + { + display(getMsg(MSG_ERR_REQUIRED_PARAM, "target"), MT_ERROR); + return; + } + } + else + { + chan = fromUnicode(e.target, e.server); + } + + // Check whether our mode string makes sense + if (!e.modestr) + { + e.modestr = ""; + if (!e.channel && arrayContains(e.server.channelTypes, chan[0])) + e.channel = new CIRCChannel(e.server, null, chan); + if (e.channel) + e.channel.pendingModeReply = true; + } + else if (!(/^([+-][a-z]+)+$/i).test(e.modestr)) + { + display(getMsg(MSG_ERR_INVALID_MODE, e.modestr), MT_ERROR); + return; + } + + var params = (e.param) ? " " + e.paramList.join(" ") : ""; + e.server.sendData("MODE " + chan + " " + fromUnicode(e.modestr, e.server) + + params + "\n"); +} + +function cmdMotif(e) +{ + var pm; + var msg; + + if (e.command.name == "channel-motif") + { + pm = e.channel.prefManager; + msg = MSG_CURRENT_CSS_CHAN; + } + else if (e.command.name == "network-motif") + { + pm = e.network.prefManager; + msg = MSG_CURRENT_CSS_NET; + } + else if (e.command.name == "user-motif") + { + pm = e.user.prefManager; + msg = MSG_CURRENT_CSS_USER; + } + else + { + pm = client.prefManager; + msg = MSG_CURRENT_CSS; + } + + if (e.motif) + { + if (e.motif == "-") + { + // delete local motif in favor of default + pm.clearPref("motif.current"); + e.motif = pm.prefs["motif.current"]; + } + else if (e.motif.search(/^(file|https?|ftp):/i) != -1) + { + // specific css file + pm.prefs["motif.current"] = e.motif; + } + else + { + // motif alias + var prefName = "motif." + e.motif; + if (client.prefManager.isKnownPref(prefName)) + { + e.motif = client.prefManager.prefs[prefName]; + } + else + { + display(getMsg(MSG_ERR_UNKNOWN_MOTIF, e.motif), MT_ERROR); + return; + } + + pm.prefs["motif.current"] = e.motif; + } + + } + + display (getMsg(msg, pm.prefs["motif.current"])); +} + +function cmdList(e) +{ + if (!e.channelName) + { + e.channelName = ""; + var c = e.server.channelCount; + if ((c > client.SAFE_LIST_COUNT) && !("listWarned" in e.network)) + { + client.munger.getRule(".inline-buttons").enabled = true; + display(getMsg(MSG_LIST_CHANCOUNT, [c, "list"]), MT_WARN); + client.munger.getRule(".inline-buttons").enabled = false; + e.network.listWarned = true; + return; + } + } + + e.network.list(e.channelName); +} + +function cmdListPlugins(e) +{ + function listPlugin(plugin, i) + { + var enabled; + if ((plugin.API > 0) || ("disablePlugin" in plugin.scope)) + enabled = plugin.enabled; + else + enabled = MSG_ALWAYS; + + display(getMsg(MSG_FMT_PLUGIN1, [i, plugin.url])); + display(getMsg(MSG_FMT_PLUGIN2, + [plugin.id, plugin.version, enabled, plugin.status])); + display(getMsg(MSG_FMT_PLUGIN3, plugin.description)); + } + + if (e.plugin) + { + listPlugin(e.plugin, 0); + return; + } + + var i = 0; + for (var k in client.plugins) + listPlugin(client.plugins[k], i++); + + if (i == 0) + display(MSG_NO_PLUGINS); +} + +function cmdRlist(e) +{ + try + { + var re = new RegExp(e.regexp, "i"); + } + catch (ex) + { + display(MSG_ERR_INVALID_REGEX, MT_ERROR); + return; + } + + var c = e.server.channelCount; + if ((c > client.SAFE_LIST_COUNT) && !("listWarned" in e.network)) + { + client.munger.getRule(".inline-buttons").enabled = true; + display(getMsg(MSG_LIST_CHANCOUNT, [c, "rlist " + e.regexp]), MT_WARN); + client.munger.getRule(".inline-buttons").enabled = false; + e.network.listWarned = true; + return; + } + e.network.list(re); +} + +function cmdReloadUI(e) +{ + if (!("getConnectionCount" in client) || + client.getConnectionCount() == 0) + { + window.location.href = window.location.href; + } +} + +function cmdQuery(e) +{ + // We'd rather *not* trigger the user.start event this time. + blockEventSounds("user", "start"); + var user = openQueryTab(e.server, e.nickname); + dispatch("set-current-view", { view: user }); + + if (e.message) + _sendMsgTo(e.message, "PRIVMSG", user); + + return user; +} + +function cmdSay(e) +{ + if (!("say" in e.sourceObject)) + { + display(getMsg(MSG_ERR_IMPROPER_VIEW, "say"), MT_ERROR); + return; + } + + _sendMsgTo(e.message, "PRIVMSG", e.sourceObject) +} + +function cmdMsg(e) +{ + var target = e.server.addTarget(e.nickname); + _sendMsgTo(e.message, "PRIVMSG", target, e.sourceObject); +} + +function _sendMsgTo(message, msgType, target, displayObj) +{ + if (!displayObj) + displayObj = target; + + + var msg = filterOutput(message, msgType, target); + + var o = getObjectDetails(target); + var lines = o.server ? o.server.splitLinesForSending(msg, true) : [msg]; + + for (var i = 0; i < lines.length; i++) + { + msg = lines[i]; + if (!(o.server && o.server.caps["echo-message"])) + { + client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"]; + displayObj.display(msg, msgType, "ME!", target); + client.munger.getRule(".mailto").enabled = false; + } + if (msgType == "PRIVMSG") + target.say(msg); + else if (msgType == "NOTICE") + target.notice(msg); + else if (msgType == "ACTION") + target.act(msg); + } +} + +function cmdNick(e) +{ + if (!e.nickname) + { + var curNick; + if (e.server && e.server.isConnected) + curNick = e.server.me.unicodeName; + else if (e.network) + curNick = e.network.prefs["nickname"]; + else + curNick = client.prefs["nickname"]; + + e.nickname = prompt(MSG_NICK_PROMPT, curNick); + if (e.nickname == null) + return; + e.nickname = e.nickname.replace(/ /g, "_"); + } + + if (e.server && e.server.isConnected) + e.server.changeNick(e.nickname); + + if (e.network) + { + /* We want to save in all non-online cases, including NET_CONNECTING, + * as we will only get a NICK reply if we are completely connected. + */ + if (e.network.state == NET_ONLINE) + { + e.network.pendingNickChange = e.nickname; + } + else + { + e.network.prefs["nickname"] = e.nickname; + e.network.preferredNick = e.nickname; + } + } + else + { + client.prefs["nickname"] = e.nickname; + updateTitle(client); + } +} + +function cmdNotice(e) +{ + var target = e.server.addTarget(e.nickname); + _sendMsgTo(e.message, "NOTICE", target, e.sourceObject); +} + +function cmdQuote(e) +{ + /* Check we are connected, or at least pretending to be connected, so this + * can actually send something. The only thing that's allowed to send + * before the 001 is PASS, so if the command is not that and the net is not + * online, we stop too. + */ + if ((e.network.state != NET_ONLINE) && + (!e.server.isConnected || !e.ircCommand.match(/^\s*PASS/i))) + { + feedback(e, MSG_ERR_NOT_CONNECTED); + return; + } + e.server.sendData(fromUnicode(e.ircCommand) + "\n", e.sourceObject); +} + +function cmdEval(e) +{ + var sourceObject = e.sourceObject; + + try + { + sourceObject.doEval = function (__s) { return eval(__s); } + if (e.command.name == "eval") + sourceObject.display(e.expression, MT_EVALIN); + var rv = String(sourceObject.doEval (e.expression)); + if (e.command.name == "eval") + sourceObject.display(rv, MT_EVALOUT); + + } + catch (ex) + { + sourceObject.display(String(ex), MT_ERROR); + } +} + +function cmdFocusInput(e) +{ + const WWATCHER_CTRID = "@mozilla.org/embedcomp/window-watcher;1"; + const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher; + + var watcher = + Components.classes[WWATCHER_CTRID].getService(nsIWindowWatcher); + if (watcher.activeWindow == window) + client.input.focus(); + else + document.commandDispatcher.focusedElement = client.input; +} + +function cmdGotoStartup(e) +{ + openStartupURLs(); +} + +function cmdGotoURL(e) +{ + if (/^ircs?:/.test(e.url)) + { + gotoIRCURL(e.url); + return; + } + + if (/^x-irc-dcc-(chat|file):[0-9a-fA-F]+$/.test(e.url)) + { + var view = client.dcc.findByID(e.url.substr(15)); + if (view) + dispatch("set-current-view", {view: view}); + return; + } + + if (/^x-cz-command:/.test(e.url)) + { + var ary = e.url.match(/^x-cz-command:(.*)$/i); + e.sourceObject.dispatch(decodeURI(ary[1]), + {isInteractive: true, source: e.source}); + return; + } + + try + { + var uri = Services.io.newURI(e.url, "UTF-8"); + } + catch (ex) + { + // Given "goto-url faq bar", expand to "http://.../faq/#bar" + var localeURLKey = "msg.localeurl." + e.url; + var hash = (("anchor" in e) && e.anchor) ? "#" + e.anchor : ""; + if (localeURLKey != getMsg(localeURLKey)) + dispatch(e.command.name + " " + getMsg(localeURLKey) + hash); + else + display(getMsg(MSG_ERR_INVALID_URL, e.url), MT_ERROR); + + dispatch("focus-input"); + return; + } + + var browserWin = getWindowByType("navigator:browser"); + var location = browserWin ? browserWin.gBrowser.currentURI.spec : null; + var action = e.command.name; + let where = "current"; + + // We don't want to replace ChatZilla running in a tab. + if ((action == "goto-url-newwin") || + ((action == "goto-url") && location && + location.startsWith("chrome://chatzilla/content/"))) + { + where = "window"; + } + + if (action == "goto-url-newtab") + { + where = e.shiftKey ? "tabshifted" : "tab"; + } + + try + { + let loadInBackground = + Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground"); + openLinkIn(e.url, where, { inBackground: loadInBackground }); + } + catch (ex) + { + dd(formatException(ex)); + } + dispatch("focus-input"); +} + +function cmdCTCP(e) +{ + var obj = e.server.addTarget(e.target); + obj.ctcp(e.code, e.params); +} + +function cmdJoin(e) +{ + /* This check makes sure we only check if the *user* entered anything, and + * ignore any contextual information, like the channel the command was + * run on. + */ + if ((!e.hasOwnProperty("channelName") || !e.channelName) && + !e.channelToJoin) + { + if (client.joinDialog) + { + client.joinDialog.setNetwork(e.network); + client.joinDialog.focus(); + return; + } + + window.openDialog("chrome://chatzilla/content/channels.xul", "", + "resizable=yes", + { client: client, network: e.network || null, + opener: window }); + return null; + } + + var chan; + if (!e.channelToJoin) + { + if (!("charset" in e)) + { + e.charset = null; + } + else if (e.charset && !checkCharset(e.charset)) + { + display (getMsg(MSG_ERR_INVALID_CHARSET, e.charset), MT_ERROR); + return null; + } + + if (e.channelName.search(",") != -1) + { + // We can join multiple channels! Woo! + var chans = e.channelName.split(","); + var keys = []; + if (e.key) + keys = e.key.split(","); + for (var c in chans) + { + chan = dispatch("join", { network: e.network, + server: e.server, + charset: e.charset, + channelName: chans[c], + key: keys.shift() }); + } + return chan; + } + + if ((arrayIndexOf(["#", "&", "+", "!"], e.channelName[0]) == -1) && + (arrayIndexOf(e.server.channelTypes, e.channelName[0]) == -1)) + { + e.channelName = e.server.channelTypes[0] + e.channelName; + } + + var charset = e.charset ? e.charset : e.network.prefs["charset"]; + chan = e.server.addChannel(e.channelName, charset); + if (e.charset) + chan.prefs["charset"] = e.charset; + } + else + { + chan = e.channelToJoin; + } + + e.key = client.tryToGetLogin(chan.getURL(), "chan", "*", e.key, false, ""); + chan.join(e.key); + + /* !-channels are "safe" channels, and get a server-generated prefix. For + * this reason, we shouldn't do anything client-side until the server + * replies (since the reply will have the appropriate prefix). */ + if (chan.unicodeName[0] != "!") + { + dispatch("create-tab-for-view", { view: chan }); + dispatch("set-current-view", { view: chan }); + } + + return chan; +} + +function cmdLeave(e) +{ + function leaveChannel(channelName) + { + var channelToLeave; + // This function will return true if we should continue processing + // channel names. If we discover that we were passed an invalid channel + // name, but have a channel on the event, we'll just leave that channel + // with the full message (including what we thought was a channel name) + // and return false in order to not process the rest of what we thought + // was a channel name. If there's a genuine error, e.g. because the user + // specified a non-existing channel and isn't in a channel either, we + // will also return a falsy value + var shouldContinue = true; + if (arrayIndexOf(e.server.channelTypes, channelName[0]) == -1) + { + // No valid prefix character. Check they really meant a channel... + var valid = false; + for (var i = 0; i < e.server.channelTypes.length; i++) + { + // Hmm, not ideal... + var chan = e.server.getChannel(e.server.channelTypes[i] + + channelName); + if (chan) + { + // Yes! They just missed that single character. + channelToLeave = chan; + valid = true; + break; + } + } + + // We can only let them get away here if we've got a channel. + if (!valid) + { + if (e.channel) + { + /* Their channel name was invalid, but we have a channel + * view, so we'll assume they did "/leave part msg". + * NB: we use e.channelName here to get the full channel + * name before we (may have) split it. + */ + e.reason = e.channelName + (e.reason ? " " + e.reason : ""); + channelToLeave = e.channel; + shouldContinue = false; + } + else + { + display(getMsg(MSG_ERR_UNKNOWN_CHANNEL, channelName), + MT_ERROR); + return; + } + } + } + else + { + // Valid prefix, so get real channel (if it exists...). + channelToLeave = e.server.getChannel(channelName); + if (!channelToLeave) + { + display(getMsg(MSG_ERR_UNKNOWN_CHANNEL, channelName), + MT_ERROR); + return; + } + } + + if (!("deleteWhenDone" in e)) + e.deleteWhenDone = client.prefs["deleteOnPart"]; + + /* If it's not active, we're not actually in it, even though the view is + * still here. + */ + if (channelToLeave.active) + { + channelToLeave.deleteWhenDone = e.deleteWhenDone; + + if (!e.reason) + e.reason = ""; + + e.server.sendData("PART " + channelToLeave.encodedName + " :" + + fromUnicode(e.reason, channelToLeave) + "\n"); + } + else + { + /* We can leave the channel when not active + * this will close the view and prevent rejoin after a reconnect + */ + if (channelToLeave.joined) + channelToLeave.joined = false; + + if (e.deleteWhenDone) + channelToLeave.dispatch("delete-view"); + } + + return shouldContinue; + }; + + if (!e.server) + { + display(getMsg(MSG_ERR_IMPROPER_VIEW, e.command.name), MT_ERROR); + return; + } + + if (!e.hasOwnProperty("channelName") && e.channel) + e.channelName = e.channel.unicodeName; + + if (e.hasOwnProperty("channelName")) + { + if (!e.channelName) + { + // No channel specified and command not sent from a channel view + display(getMsg(MSG_ERR_NEED_CHANNEL, e.command.name), MT_ERROR); + return; + } + + + var channels = e.channelName.split(","); + for (var i = 0; i < channels.length; i++) + { + // Skip empty channel names: + if (!channels[i]) + continue; + + // If we didn't successfully leave, stop processing the + // rest of the channels: + if (!leaveChannel(channels[i])) + break; + } + } +} + +function cmdMarker(e) +{ + if (!client.initialized) + return; + + var view = e.sourceObject; + if (!("setActivityMarker" in e.sourceObject)) + return; + + var marker = e.sourceObject.getActivityMarker(); + if ((e.command.name == "marker") && (marker == null)) + { + // Marker is not currently set but user wants to scroll to it, + // so we just call set like normal. + e.command.name = "marker-set"; + } + + switch(e.command.name) + { + case "marker": /* Scroll to the marker. */ + e.sourceObject.scrollToElement("marker", "center"); + break; + case "marker-set": /* Set (or reset) the marker. */ + e.sourceObject.setActivityMarker(true); + e.sourceObject.scrollToElement("marker", "center"); + break; + case "marker-clear": /* Clear the marker. */ + e.sourceObject.setActivityMarker(false); + break; + default: + view.display(MSG_ERR_UNKNOWN_COMMAND, e.command.name); + } +} + +function cmdReload(e) +{ + dispatch("load " + e.plugin.url); +} + +function cmdLoad(e) +{ + if (!e.scope) + e.scope = new Object(); + + if (!("plugin" in e.scope)) + { + e.scope.plugin = { url: e.url, id: MSG_UNKNOWN, version: -1, + description: "", status: MSG_LOADING, enabled: false, + PluginAPI: 1, cwd: e.url.match(/^(.*?)[^\/]+$/)[1]}; + + } + + var plugin = e.scope.plugin; + plugin.scope = e.scope; + + try + { + var rvStr; + var rv = rvStr = client.load(e.url, e.scope); + let oldPlugin = getPluginByURL(e.url); + if (oldPlugin && !disablePlugin(oldPlugin, true)) + { + display(getMsg(MSG_ERR_SCRIPTLOAD, e.url)); + return null; + } + + if ("init" in plugin) + { + // Sanity check plugin's methods and properties: + var okay = false; + if (!("id" in plugin) || (plugin.id == MSG_UNKNOWN)) + display(getMsg(MSG_ERR_PLUGINAPI_NOID, e.url)); + else if (!(plugin.id.match(/^[A-Za-z0-9-_]+$/))) + display(getMsg(MSG_ERR_PLUGINAPI_FAULTYID, e.url)); + else if (!("enable" in plugin)) + display(getMsg(MSG_ERR_PLUGINAPI_NOENABLE, e.url)); + else if (!("disable" in plugin)) + display(getMsg(MSG_ERR_PLUGINAPI_NODISABLE, e.url)); + else + okay = true; + + if (!okay) + { + display (getMsg(MSG_ERR_SCRIPTLOAD, e.url)); + return null; + } + + plugin.API = 1; + plugin.prefary = [["enabled", true, "hidden"]]; + rv = rvStr = plugin.init(e.scope); + + var branch = "extensions.irc.plugins." + plugin.id + "."; + var prefManager = new PrefManager(branch, client.defaultBundle); + prefManager.addPrefs(plugin.prefary); + plugin.prefManager = prefManager; + plugin.prefs = prefManager.prefs; + if ("onPrefChanged" in plugin) + prefManager.addObserver(plugin); + client.prefManager.addObserver(prefManager); + client.prefManagers.push(prefManager); + } + else + { + plugin.API = 0; + if ("initPlugin" in e.scope) + rv = rvStr = e.scope.initPlugin(e.scope); + plugin.enabled = true; + } + plugin.status = "loaded"; + + if (typeof rv == "function") + rvStr = "function"; + + if (!plugin.id) + plugin.id = 'plugin' + randomString(8); + + client.plugins[plugin.id] = plugin; + + feedback(e, getMsg(MSG_SUBSCRIPT_LOADED, [e.url, rvStr]), MT_INFO); + + if ((plugin.API > 0) && plugin.prefs["enabled"]) + dispatch("enable-plugin " + plugin.id); + return {rv: rv}; + } + catch (ex) + { + display (getMsg(MSG_ERR_SCRIPTLOAD, e.url)); + display (formatException(ex), MT_ERROR); + } + + return null; +} + +function cmdWho(e) +{ + e.network.pendingWhoReply = true; + e.server.LIGHTWEIGHT_WHO = false; + e.server.who(e.rest); +} + +function cmdWhoIs(e) +{ + if (!isinstance(e.network.whoisList, Object)) + e.network.whoisList = {}; + + for (var i = 0; i < e.nicknameList.length; i++) + { + if ((i < e.nicknameList.length - 1) && + (e.server.toLowerCase(e.nicknameList[i]) == + e.server.toLowerCase(e.nicknameList[i + 1]))) + { + e.server.whois(e.nicknameList[i] + " " + e.nicknameList[i]); + i++; + } + else + { + e.server.whois(e.nicknameList[i]); + } + e.network.whoisList[e.server.toLowerCase(e.nicknameList[i])] = null; + } +} + +function cmdWhoIsIdle(e) +{ + for (var i = 0; i < e.nicknameList.length; i++) + e.server.whois(e.nicknameList[i] + " " + e.nicknameList[i]); +} + +function cmdWhoWas(e) +{ + e.server.whowas(e.nickname, e.limit); +} + +function cmdTopic(e) +{ + if (!e.newTopic) + e.server.sendData("TOPIC " + e.channel.encodedName + "\n"); + else + e.channel.setTopic(e.newTopic); +} + +function cmdAbout(e) +{ + if (e.source) + { + if ("aboutDialog" in client) + return client.aboutDialog.focus(); + + window.openDialog("chrome://chatzilla/content/about/about.xul", "", + "chrome,dialog", { client: client }); + } + else + { + var ver = CIRCServer.prototype.VERSION_RPLY; + client.munger.getRule(".inline-buttons").enabled = true; + display(getMsg(MSG_ABOUT_VERSION, [ver, "about"])); + display(MSG_ABOUT_HOMEPAGE); + client.munger.getRule(".inline-buttons").enabled = false; + } +} + +function cmdAlias(e) +{ + var aliasDefs = client.prefs["aliases"]; + function getAlias(commandName) + { + for (var i = 0; i < aliasDefs.length; ++i) + { + var ary = aliasDefs[i].match(/^(.*?)\s*=\s*(.*)$/); + if (ary[1] == commandName) + return [i, ary[2]]; + } + + return null; + }; + + var ary; + + if ((e.commandList == "-") || (e.command.name == "unalias")) + { + /* remove alias */ + ary = getAlias(e.aliasName); + if (!ary) + { + display(getMsg(MSG_NOT_AN_ALIAS, e.aliasName), MT_ERROR); + return; + } + + // Command Manager is updated when the preference changes. + arrayRemoveAt(aliasDefs, ary[0]); + aliasDefs.update(); + + feedback(e, getMsg(MSG_ALIAS_REMOVED, e.aliasName)); + } + else if (e.aliasName && e.commandList) + { + /* add/change alias */ + ary = getAlias(e.aliasName); + if (ary) + aliasDefs[ary[0]] = e.aliasName + " = " + e.commandList; + else + aliasDefs.push(e.aliasName + " = " + e.commandList); + + // Command Manager is updated when the preference changes. + aliasDefs.update(); + + feedback(e, getMsg(MSG_ALIAS_CREATED, [e.aliasName, e.commandList])); + } + else if (e.aliasName) + { + /* display alias */ + ary = getAlias(e.aliasName); + if (!ary) + display(getMsg(MSG_NOT_AN_ALIAS, e.aliasName), MT_ERROR); + else + display(getMsg(MSG_FMT_ALIAS, [e.aliasName, ary[1]])); + } + else + { + /* list aliases */ + if (aliasDefs.length == 0) + { + display(MSG_NO_ALIASES); + } + else + { + for (var i = 0; i < aliasDefs.length; ++i) + { + ary = aliasDefs[i].match(/^(.*?)\s*=\s*(.*)$/); + if (ary) + display(getMsg(MSG_FMT_ALIAS, [ary[1], ary[2]])); + else + display(getMsg(MSG_ERR_BADALIAS, aliasDefs[i])); + } + } + } +} + +function cmdAway(e) +{ + function sendToAllNetworks(command, reason) + { + for (var n in client.networks) + { + var net = client.networks[n]; + if (net.primServ && (net.state == NET_ONLINE)) + { + // If we can override the network's away state, or they are + // already idly-away, or they're not away to begin with: + if (overrideAway || net.isIdleAway || !net.prefs["away"]) + { + net.dispatch(command, {reason: reason }); + net.isIdleAway = (e.command.name == "idle-away"); + } + } + } + }; + + // Idle away shouldn't override away state set by the user. + var overrideAway = (e.command.name.indexOf("idle") != 0); + + if ((e.command.name == "away") || (e.command.name == "custom-away") || + (e.command.name == "idle-away")) + { + /* going away */ + if (e.command.name == "custom-away") + { + e.reason = prompt(MSG_AWAY_PROMPT); + // prompt() returns null for cancelling, a string otherwise (even if empty). + if (e.reason == null) + return; + } + // No parameter, or user entered nothing in the prompt. + if (!e.reason) + e.reason = MSG_AWAY_DEFAULT; + + // Update away list (remove from current location). + for (var i = 0; i < client.awayMsgs.length; i++) + { + if (client.awayMsgs[i].message == e.reason) + { + client.awayMsgs.splice(i, 1); + break; + } + } + // Always put new item at start. + var newMsg = { message: e.reason }; + client.awayMsgs.unshift(newMsg); + // Make sure we've not exceeded the limit set. + if (client.awayMsgs.length > client.awayMsgCount) + client.awayMsgs.splice(client.awayMsgCount); + // And now, to save the list! + try + { + var awayFile = new nsLocalFile(client.prefs["profilePath"]); + awayFile.append("awayMsgs.txt"); + var awayLoader = new TextSerializer(awayFile); + if (awayLoader.open(">")) + { + awayLoader.serialize(client.awayMsgs); + awayLoader.close(); + } + } + catch(ex) + { + display(getMsg(MSG_ERR_AWAY_SAVE, formatException(ex)), MT_ERROR); + } + + // Actually do away stuff, is this on a specific network? + if (e.server) + { + var normalNick = e.network.prefs["nickname"]; + var awayNick = e.network.prefs["awayNick"]; + if (e.network.state == NET_ONLINE) + { + // Postulate that if normal nick and away nick are the same, + // user doesn't want to change nicks: + if (awayNick && (normalNick != awayNick)) + e.server.changeNick(awayNick); + e.server.sendData("AWAY :" + fromUnicode(e.reason, e.network) + + "\n"); + } + if (awayNick && (normalNick != awayNick)) + e.network.preferredNick = awayNick; + e.network.prefs["away"] = e.reason; + } + else + { + // Client view, do command for all networks. + sendToAllNetworks("away", e.reason); + client.prefs["away"] = e.reason; + + // Don't tell people how to get back if they're idle: + var idleMsgParams = [e.reason, client.prefs["awayIdleTime"]]; + if (e.command.name == "idle-away") + var msg = getMsg(MSG_IDLE_AWAY_ON, idleMsgParams); + else + msg = getMsg(MSG_AWAY_ON, e.reason); + + // Display on the *client* tab, or on the current tab iff + // there's nowhere else they'll hear about it: + if (("frame" in client) && client.frame) + client.display(msg); + else if (!client.getConnectedNetworks()) + display(msg); + } + } + else + { + /* returning */ + if (e.server) + { + if (e.network.state == NET_ONLINE) + { + var curNick = e.server.me.unicodeName; + var awayNick = e.network.prefs["awayNick"]; + if (awayNick && (curNick == awayNick)) + e.server.changeNick(e.network.prefs["nickname"]); + e.server.sendData("AWAY\n"); + } + // Go back to old nick, even if not connected: + if (awayNick && (curNick == awayNick)) + e.network.preferredNick = e.network.prefs["nickname"]; + e.network.prefs["away"] = ""; + } + else + { + client.prefs["away"] = ""; + // Client view, do command for all networks. + sendToAllNetworks("back"); + if (("frame" in client) && client.frame) + client.display(MSG_AWAY_OFF); + else if (!client.getConnectedNetworks()) + display(MSG_AWAY_OFF); + } + } +} + +function cmdOpenAtStartup(e) +{ + var origURL = e.sourceObject.getURL(); + var url = makeCanonicalIRCURL(origURL); + var list = client.prefs["initialURLs"]; + ensureCachedCanonicalURLs(list); + var index = arrayIndexOf(list.canonicalURLs, url); + + if (e.toggle == null) + { + if (index == -1) + display(getMsg(MSG_STARTUP_NOTFOUND, url)); + else + display(getMsg(MSG_STARTUP_EXISTS, url)); + return; + } + + e.toggle = getToggle(e.toggle, (index != -1)); + + if (e.toggle) + { + // yes, please open at startup + if (index == -1) + { + list.push(origURL); + list.update(); + display(getMsg(MSG_STARTUP_ADDED, url)); + } + else + { + display(getMsg(MSG_STARTUP_EXISTS, url)); + } + } + else + { + // no, please don't open at startup + if (index != -1) + { + arrayRemoveAt(list, index); + list.update(); + display(getMsg(MSG_STARTUP_REMOVED, url)); + } + else + { + display(getMsg(MSG_STARTUP_NOTFOUND, url)); + } + } +} + +function cmdOper(e) +{ + e.password = client.tryToGetLogin(e.server.getURL(), "oper", e.opername, + e.password, true, MSG_NEED_OPER_PASSWORD); + + if (!e.password) + return; + + e.server.sendData("OPER " + fromUnicode(e.opername, e.server) + " " + + fromUnicode(e.password, e.server) + "\n"); +} + +function cmdPing (e) +{ + e.network.dispatch("ctcp", { target: e.nickname, code: "PING" }); +} + +function cmdPref (e) +{ + var msg; + var pm; + + if (e.command.name == "network-pref") + { + pm = e.network.prefManager; + msg = MSG_FMT_NETPREF; + } + else if (e.command.name == "channel-pref") + { + pm = e.channel.prefManager; + msg = MSG_FMT_CHANPREF; + } + else if (e.command.name == "plugin-pref") + { + pm = e.plugin.prefManager; + msg = MSG_FMT_PLUGINPREF; + } + else if (e.command.name == "user-pref") + { + pm = e.user.prefManager; + msg = MSG_FMT_USERPREF; + } + else + { + pm = client.prefManager; + msg = MSG_FMT_PREF; + } + + var ary = pm.listPrefs(e.prefName); + if (ary.length == 0) + { + display (getMsg(MSG_ERR_UNKNOWN_PREF, [e.prefName]), + MT_ERROR); + return false; + } + + if (e.prefValue == "-") + e.deletePref = true; + + if (e.deletePref) + { + if (!(e.prefName in pm.prefRecords)) + { + display(getMsg(MSG_ERR_UNKNOWN_PREF, [e.prefName]), MT_ERROR); + return false; + } + + try + { + pm.clearPref(e.prefName); + } + catch (ex) + { + // ignore exception generated by clear of nonexistant pref + if (!("result" in ex) || + ex.result != Components.results.NS_ERROR_UNEXPECTED) + { + throw ex; + } + } + + var prefValue = pm.prefs[e.prefName]; + feedback (e, getMsg(msg, [e.prefName, pm.prefs[e.prefName]])); + return true; + } + + if (e.prefValue) + { + if (!(e.prefName in pm.prefRecords)) + { + display(getMsg(MSG_ERR_UNKNOWN_PREF, [e.prefName]), MT_ERROR); + return false; + } + + var r = pm.prefRecords[e.prefName]; + var def, type; + + if (typeof r.defaultValue == "function") + def = r.defaultValue(e.prefName); + else + def = r.defaultValue; + + type = typeof def; + + switch (type) + { + case "number": + e.prefValue = Number(e.prefValue); + break; + case "boolean": + e.prefValue = (e.prefValue.toLowerCase() == "true"); + break; + case "string": + break; + default: + if (isinstance(e.prefValue, Array)) + e.prefValue = e.prefValue.join("; "); + if (isinstance(def, Array)) + e.prefValue = pm.stringToArray(e.prefValue); + break; + } + + pm.prefs[e.prefName] = e.prefValue; + if (isinstance(e.prefValue, Array)) + e.prefValue = e.prefValue.join("; "); + feedback (e, getMsg(msg, [e.prefName, e.prefValue])); + } + else + { + for (var i = 0; i < ary.length; ++i) + { + var value; + if (isinstance(pm.prefs[ary[i]], Array)) + value = pm.prefs[ary[i]].join("; "); + else + value = pm.prefs[ary[i]]; + + feedback(e, getMsg(msg, [ary[i], value])); + } + } + + return true; +} + +function cmdPrint(e) +{ + if (("frame" in e.sourceObject) && e.sourceObject.frame && + getContentWindow(e.sourceObject.frame)) + { + getContentWindow(e.sourceObject.frame).print(); + } + else + { + display(MSG_ERR_UNABLE_TO_PRINT); + } +} + +function cmdVersion(e) +{ + if (e.nickname) + e.network.dispatch("ctcp", { target: e.nickname, code: "VERSION"}); + else + e.server.sendData(fromUnicode("VERSION") + "\n", e.sourceObject); +} + +function cmdEcho(e) +{ + client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"]; + display(e.message); + client.munger.getRule(".mailto").enabled = false; +} + +function cmdInvite(e) +{ + var channel; + + if (e.channelName) + { + channel = e.server.getChannel(e.channelName); + if (!channel) + { + display(getMsg(MSG_ERR_UNKNOWN_CHANNEL, e.channelName), MT_ERROR); + return; + } + } + else if (e.channel) + { + channel = e.channel; + } + else + { + display(getMsg(MSG_ERR_NO_CHANNEL, e.command.name), MT_ERROR); + return; + } + + channel.invite(e.nickname); +} + +function cmdKick(e) +{ + if (e.userList) + { + if (e.command.name == "kick-ban") + { + e.sourceObject.dispatch("ban", { userList: e.userList, + canonNickList: e.canonNickList, + user: e.user, + nickname: e.user.encodedName }); + } + + /* Note that we always do /kick below; the /ban is covered above. + * Also note that we are required to pass the nickname, to satisfy + * the dispatching of the command (which is defined with a required + * <nickname> parameter). It's not required for /ban, above, but it + * seems prudent to include it anyway. + */ + for (var i = 0; i < e.userList.length; i++) + { + var e2 = { user: e.userList[i], + nickname: e.userList[i].encodedName }; + e.sourceObject.dispatch("kick", e2); + } + return; + } + + if (!e.user) + e.user = e.channel.getUser(e.nickname); + + if (!e.user) + { + display(getMsg(MSG_ERR_UNKNOWN_USER, e.nickname), MT_ERROR); + return; + } + + if (e.command.name == "kick-ban") + e.sourceObject.dispatch("ban", { nickname: e.user.encodedName }); + + e.user.kick(e.reason); +} + +function cmdKnock(e) +{ + var rest = (e.reason ? " :" + fromUnicode(e.reason, e.server) : "") + "\n"; + e.server.sendData("KNOCK " + fromUnicode(e.channelName, e.server) + rest); +} + +function cmdClient(e) +{ + if (!("messages" in client)) + { + client.display(MSG_WELCOME, "HELLO"); + dispatch("set-current-view", { view: client }); + dispatch("help", { hello: true }); + dispatch("networks"); + } + else + { + dispatch("set-current-view", { view: client }); + } +} + +function cmdNotify(e) +{ + var net = e.network; + var supports_monitor = net.primServ.supports["monitor"]; + + if (!e.nickname) + { + if (net.prefs["notifyList"].length > 0) + { + if (supports_monitor) + { + // Just get the status of the monitor list from the server. + net.primServ.sendData("MONITOR S\n"); + } + else + { + /* delete the lists and force a ISON check, this will + * print the current online/offline status when the server + * responds */ + delete net.onList; + delete net.offList; + onNotifyTimeout(); + } + } + else + { + display(MSG_NO_NOTIFY_LIST); + } + } + else + { + var adds = new Array(); + var subs = new Array(); + + for (var i in e.nicknameList) + { + var nickname = e.server.toLowerCase(e.nicknameList[i]); + var list = net.prefs["notifyList"]; + list = e.server.toLowerCase(list.join(";")).split(";"); + var idx = arrayIndexOf (list, nickname); + if (idx == -1) + { + net.prefs["notifyList"].push (nickname); + adds.push(nickname); + } + else + { + arrayRemoveAt (net.prefs["notifyList"], idx); + subs.push(nickname); + } + } + net.prefs["notifyList"].update(); + + var msgname; + + if (adds.length > 0) + { + if (supports_monitor) + net.primServ.sendMonitorList(adds, true); + + msgname = (adds.length == 1) ? MSG_NOTIFY_ADDONE : + MSG_NOTIFY_ADDSOME; + display(getMsg(msgname, arraySpeak(adds))); + } + + if (subs.length > 0) + { + if (supports_monitor) + net.primServ.sendMonitorList(subs, false); + + msgname = (subs.length == 1) ? MSG_NOTIFY_DELONE : + MSG_NOTIFY_DELSOME; + display(getMsg(msgname, arraySpeak(subs))); + } + + delete net.onList; + delete net.offList; + if (!supports_monitor) + onNotifyTimeout(); + } +} + +function cmdStalk(e) +{ + var list = client.prefs["stalkWords"]; + + if (!e.text) + { + if (list.length == 0) + display(MSG_NO_STALK_LIST); + else + { + function alphabetize(a, b) + { + var A = a.toLowerCase(); + var B = b.toLowerCase(); + if (A < B) return -1; + if (B < A) return 1; + return 0; + } + + list.sort(alphabetize); + display(getMsg(MSG_STALK_LIST, list.join(", "))); + } + return; + } + + var notStalkingWord = true; + var loweredText = e.text.toLowerCase(); + + for (var i = 0; i < list.length; ++i) + if (list[i].toLowerCase() == loweredText) + notStalkingWord = false; + + if (notStalkingWord) + { + list.push(e.text); + list.update(); + display(getMsg(MSG_STALK_ADD, e.text)); + } + else + { + display(getMsg(MSG_STALKING_ALREADY, e.text)); + } +} + +function cmdUnstalk(e) +{ + e.text = e.text.toLowerCase(); + var list = client.prefs["stalkWords"]; + + for (var i = 0; i < list.length; ++i) + { + if (list[i].toLowerCase() == e.text) + { + list.splice(i, 1); + list.update(); + display(getMsg(MSG_STALK_DEL, e.text)); + return; + } + } + + display(getMsg(MSG_ERR_UNKNOWN_STALK, e.text), MT_ERROR); +} + +function cmdUser(e) +{ + dispatch("name", {username: e.username, network: e.network, + isInteractive: e.isInteractive}); + dispatch("desc", {description: e.description, network: e.network, + isInteractive: e.isInteractive}); +} + +function cmdUserhost(e) +{ + var nickList = combineNicks(e.nicknameList, 5); + for (var i = 0; i < nickList.length; i++) + { + e.server.userhost(nickList[i]); + } +} + +function cmdUserip(e) +{ + // Check if the server supports this + if (!e.server.servCmds.userip) + { + display(getMsg(MSG_ERR_UNSUPPORTED_COMMAND, "USERIP"), MT_ERROR); + return; + } + var nickList = combineNicks(e.nicknameList, 5); + for (var i = 0; i < nickList.length; i++) + e.server.userip(nickList[i]); +} + +function cmdUsermode(e) +{ + if (e.newMode) + { + if (e.sourceObject.network) + e.sourceObject.network.prefs["usermode"] = e.newMode; + else + client.prefs["usermode"] = e.newMode; + } + else + { + if (e.server && e.server.isConnected) + { + e.server.sendData("mode " + e.server.me.encodedName + "\n"); + } + else + { + var prefs; + + if (e.network) + prefs = e.network.prefs; + else + prefs = client.prefs; + + display(getMsg(MSG_USER_MODE, + [prefs["nickname"], prefs["usermode"]]), + MT_MODE); + } + } +} + +function cmdLog(e) +{ + var view = e.sourceObject; + + if (e.state != null) + { + e.state = getToggle(e.state, view.prefs["log"]) + view.prefs["log"] = e.state; + } + else + { + if (view.prefs["log"]) + display(getMsg(MSG_LOGGING_ON, getLogPath(view))); + else + display(MSG_LOGGING_OFF); + } +} + +function cmdSave(e) +{ + var OutputProgressListener = + { + onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) + { + // Use this to access onStateChange flags + var requestSpec; + try + { + var channel = aRequest.QueryInterface(nsIChannel); + requestSpec = channel.URI.spec; + } + catch (ex) { } + + // Detect end of file saving of any file: + if (aStateFlags & nsIWebProgressListener.STATE_STOP) + { + if (aStatus == kErrorBindingAborted) + aStatus = 0; + + // We abort saving for all errors except if image src file is + // not found + var abortSaving = (aStatus != 0 && aStatus != kFileNotFound); + if (abortSaving) + { + // Cancel saving + wbp.cancelSave(); + display(getMsg(MSG_SAVE_ERR_FAILED, aMessage), MT_ERROR); + return; + } + + if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK + && wbp.currentState == nsIWBP.PERSIST_STATE_FINISHED) + { + // Let the user know: + pm = [e.sourceObject.viewName, getURLSpecFromFile(file)]; + display(getMsg(MSG_SAVE_SUCCESSFUL, pm), MT_INFO); + } + /* Check if we've finished. WebBrowserPersist screws up when we + * don't save additional files. Cope when saving html only or + * text. + */ + else if (!requestSpec && saveType > 0) + { + if (wbp) + wbp.progressListener = null; + pm = [e.sourceObject.viewName, getURLSpecFromFile(file)]; + display(getMsg(MSG_SAVE_SUCCESSFUL, pm), MT_INFO); + } + } + }, + + onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress) {}, + onLocationChange: function(aWebProgress, aRequest, aLocation) {}, + onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {}, + onSecurityChange: function(aWebProgress, aRequest, state) {}, + + QueryInterface: function(aIID) + { + if (aIID.equals(Components.interfaces.nsIWebProgressListener) + || aIID.equals(Components.interfaces.nsISupports) + || aIID.equals(Components.interfaces.nsISupportsWeakReference)) + { + return this; + } + + throw Components.results.NS_NOINTERFACE; + } + }; + + const kFileNotFound = 2152857618; + const kErrorBindingAborted = 2152398850; + + const nsIWBP = Components.interfaces.nsIWebBrowserPersist; + const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; + const nsIChannel = Components.interfaces.nsIChannel; + + var wbp = newObject("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", + nsIWBP); + wbp.progressListener = OutputProgressListener; + + var file, saveType, saveFolder, docToBeSaved, title; + var flags, fileType, charLimit; + var dialogTitle, rv, pm; + + // We want proper descriptions and no "All Files" option. + const TYPELIST = [[MSG_SAVE_COMPLETEVIEW,"*.htm;*.html"], + [MSG_SAVE_HTMLONLYVIEW,"*.htm;*.html"], + [MSG_SAVE_PLAINTEXTVIEW,"*.txt"], "$noAll"]; + // constants and variables for the wbp.saveDocument call + var saveTypes = + { + complete: 0, + htmlonly: 1, + text: 2 + }; + + if (!e.filename) + { + dialogTitle = getMsg(MSG_SAVE_DIALOGTITLE, e.sourceObject.viewName); + rv = pickSaveAs(dialogTitle, TYPELIST, e.sourceObject.viewName + + ".html"); + if (!rv.ok) + return; + saveType = rv.picker.filterIndex; + file = rv.file; + e.filename = rv.file.path; + } + else + { + try + { + // Try to use this as a path + file = nsLocalFile(e.filename); + } + catch (ex) + { + // try to use it as a URL + try + { + file = getFileFromURLSpec(e.filename); + } + catch(ex) + { + // What is the user thinking? It's not rocket science... + display(getMsg(MSG_SAVE_ERR_INVALID_PATH, e.filename), + MT_ERROR); + return; + } + } + + // Get extension and determine savetype + if (!e.savetype) + { + var extension = file.path.substr(file.path.lastIndexOf(".")); + if (extension == ".txt") + { + saveType = saveTypes["text"]; + } + else if (extension.match(/\.x?html?$/)) + { + saveType = saveTypes["complete"]; + } + else + { + // No saveType and no decent extension --> out! + var errMsg; + if (extension.indexOf(".") < 0) + errMsg = MSG_SAVE_ERR_NO_EXT; + else + errMsg = getMsg(MSG_SAVE_ERR_INVALID_EXT, extension); + display(errMsg, MT_ERROR); + return; + } + } + else + { + if (!(e.savetype in saveTypes)) + { + // no valid saveType + display(getMsg(MSG_SAVE_ERR_INVALID_SAVETYPE, e.savetype), + MT_ERROR); + return; + } + saveType = saveTypes[e.savetype]; + } + + var askforreplace = (e.isInteractive && file.exists()); + if (askforreplace && !confirm(getMsg(MSG_SAVE_FILEEXISTS, e.filename))) + return; + } + + // We don't want to convert anything, leave everything as is and replace + // old files, as the user has been prompted about that already. + wbp.persistFlags |= nsIWBP.PERSIST_FLAGS_NO_CONVERSION + | nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES + | nsIWBP.PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS + | nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES + | nsIWBP.PERSIST_FLAGS_DONT_FIXUP_LINKS + | nsIWBP.PERSIST_FLAGS_DONT_CHANGE_FILENAMES; + + // Set the document from the current view, and set a usable title + docToBeSaved = getContentDocument(e.sourceObject.frame); + var headElement = docToBeSaved.getElementsByTagName("HEAD")[0]; + var titleElements = docToBeSaved.getElementsByTagName("title"); + // Remove an existing title, there shouldn't be more than one. + if (titleElements.length > 0) + titleElements[0].parentNode.removeChild(titleElements[0]); + title = docToBeSaved.createElement("title"); + title.appendChild(docToBeSaved.createTextNode(document.title + + " (" + new Date() + ")")); + headElement.appendChild(title); + // Set standard flags, file type, saveFolder and character limit + flags = nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + fileType = "text/html"; + saveFolder = null; + charLimit = 0; + + // Do saveType specific stuff + switch (saveType) + { + case saveTypes["complete"]: + // Get the directory into which to save associated files. + saveFolder = file.clone(); + var baseName = saveFolder.leafName; + baseName = baseName.substring(0, baseName.lastIndexOf(".")); + saveFolder.leafName = getMsg(MSG_SAVE_FILES_FOLDER, baseName); + break; + // html only does not need any additional configuration + case saveTypes["text"]: + // set flags for Plain Text + flags = nsIWBP.ENCODE_FLAGS_FORMATTED; + flags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS; + flags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT; + // set the file type and set character limit to 80 + fileType = "text/plain"; + charLimit = 80; + break; + } + + try + { + wbp.saveDocument(docToBeSaved, file, saveFolder, fileType, flags, + charLimit); + } + catch (ex) + { + pm = [e.sourceObject.viewName, e.filename, ex.message]; + display(getMsg(MSG_SAVE_ERR_FAILED, pm), MT_ERROR); + } + // Error handling and finishing message is done by the listener +} + +function cmdSupports(e) +{ + var server = e.server; + var data = server.supports; + + if ("channelTypes" in server) + display(getMsg(MSG_SUPPORTS_CHANTYPES, + server.channelTypes.join(", "))); + if ("channelModes" in server) + { + display(getMsg(MSG_SUPPORTS_CHANMODESA, + server.channelModes.a.join(", "))); + display(getMsg(MSG_SUPPORTS_CHANMODESB, + server.channelModes.b.join(", "))); + display(getMsg(MSG_SUPPORTS_CHANMODESC, + server.channelModes.c.join(", "))); + display(getMsg(MSG_SUPPORTS_CHANMODESD, + server.channelModes.d.join(", "))); + } + + if ("userModes" in server) + { + var list = new Array(); + for (var m in server.userModes) + { + list.push(getMsg(MSG_SUPPORTS_USERMODE, [ + server.userModes[m].mode, + server.userModes[m].symbol + ])); + } + display(getMsg(MSG_SUPPORTS_USERMODES, list.join(", "))); + } + + var listB1 = new Array(); + var listB2 = new Array(); + var listN = new Array(); + for (var k in data) + { + if (typeof data[k] == "boolean") + { + if (data[k]) + listB1.push(k); + else + listB2.push(k); + } + else + { + listN.push(getMsg(MSG_SUPPORTS_MISCOPTION, [ k, data[k] ] )); + } + } + listB1.sort(); + listB2.sort(); + listN.sort(); + display(getMsg(MSG_SUPPORTS_FLAGSON, listB1.join(", "))); + display(getMsg(MSG_SUPPORTS_FLAGSOFF, listB2.join(", "))); + display(getMsg(MSG_SUPPORTS_MISCOPTIONS, listN.join(", "))); + + var listCaps = new Array(); + var listCapsEnabled = new Array(); + for (var cap in server.caps) + { + listCaps.push(cap); + if (server.caps[cap]) + listCapsEnabled.push(cap); + } + if (listCaps.length > 0) + { + listCaps.sort(); + listCapsEnabled.sort(); + display(getMsg(MSG_SUPPORTS_CAPS, listCaps.join(", "))); + display(getMsg(MSG_SUPPORTS_CAPSON, listCapsEnabled.join(", "))); + } +} + +function cmdDoCommand(e) +{ + if (e.cmdName == "cmd_mozillaPrefs") + { + // Open SeaMonkey preferences. + goPreferences("navigator_pane"); + } + else if (e.cmdName == "cmd_chatzillaPrefs") + { + var prefWin = getWindowByType("irc:chatzilla:config"); + if (!prefWin) + { + window.openDialog('chrome://chatzilla/content/config.xul', '', + 'chrome,resizable,dialog=no', window); + } + else + { + prefWin.focus(); + } + } + else if (e.cmdName == "cmd_selectAll") + { + var userList = document.getElementById("user-list"); + var elemFocused = document.commandDispatcher.focusedElement; + + if (userList.view && (elemFocused == userList)) + userList.view.selection.selectAll(); + else + doCommand("cmd_selectAll"); + } + else + { + doCommand(e.cmdName); + } +} + +function cmdTime(e) +{ + if (e.nickname) + e.network.dispatch("ctcp", { target: e.nickname, code: "TIME"}); + else + e.server.sendData(fromUnicode("TIME") + "\n", e.sourceObject); +} + +function cmdTimestamps(e) +{ + var view = e.sourceObject; + + if (e.toggle != null) + { + e.toggle = getToggle(e.toggle, view.prefs["timestamps"]) + view.prefs["timestamps"] = e.toggle; + } + else + { + display(getMsg(MSG_FMT_PREF, ["timestamps", + view.prefs["timestamps"]])); + } +} + +function cmdSetCurrentView(e) +{ + if ("lockView" in e.view) + delete e.view.lockView; + + setCurrentObject(e.view); +} + +function cmdJumpToAnchor(e) +{ + if (e.hasOwnProperty("channelName")) + { + e.channel = new CIRCChannel(e.server, e.channelName); + } + else if (!e.channel) + { + display(getMsg(MSG_ERR_REQUIRED_PARAM, "channel-name"), MT_ERROR); + return; + } + if (!e.channel.frame) + { + display(getMsg(MSG_JUMPTO_ERR_NOCHAN, e.channel.unicodeName), MT_ERROR); + return; + } + + var document = getContentDocument(e.channel.frame); + var row = document.getElementById(e.anchor); + + if (!row) + { + display(getMsg(MSG_JUMPTO_ERR_NOANCHOR), MT_ERROR); + return; + } + + dispatch("set-current-view", {view: e.channel}); + e.channel.scrollToElement(row, "center"); +} + +function cmdIdentify(e) +{ + e.password = client.tryToGetLogin(e.server.parent.getURL(), "nick", + e.server.me.name, e.password, true, + MSG_NEED_IDENTIFY_PASSWORD); + if (!e.password) + return; + + e.server.sendData("NS IDENTIFY " + fromUnicode(e.password, e.server) + + "\n"); +} + +function cmdIgnore(e) +{ + if (("mask" in e) && e.mask) + { + e.mask = e.server.toLowerCase(e.mask); + + if (e.command.name == "ignore") + { + if (e.network.ignore(e.mask)) + display(getMsg(MSG_IGNORE_ADD, e.mask)); + else + display(getMsg(MSG_IGNORE_ADDERR, e.mask)); + } + else + { + if (e.network.unignore(e.mask)) + display(getMsg(MSG_IGNORE_DEL, e.mask)); + else + display(getMsg(MSG_IGNORE_DELERR, e.mask)); + } + // Update pref: + var ignoreList = keys(e.network.ignoreList); + e.network.prefs["ignoreList"] = ignoreList; + e.network.prefs["ignoreList"].update(); + } + else + { + var list = new Array(); + for (var m in e.network.ignoreList) + list.push(m); + if (list.length == 0) + display(MSG_IGNORE_LIST_1); + else + display(getMsg(MSG_IGNORE_LIST_2, arraySpeak(list))); + } +} + +function cmdFont(e) +{ + var view = client; + var pref, val, pVal; + + if (e.command.name == "font-family") + { + pref = "font.family"; + val = e.font; + + // Save new value, then display pref value. + if (val) + view.prefs[pref] = val; + + display(getMsg(MSG_FONTS_FAMILY_FMT, view.prefs[pref])); + } + else if (e.command.name == "font-size") + { + pref = "font.size"; + val = e.fontSize; + + // Ok, we've got an input. + if (val) + { + // Get the current value, use user's default if needed. + pVal = view.prefs[pref]; + if (pVal == 0) + pVal = getDefaultFontSize(); + + // Handle user's input... + switch(val) { + case "default": + val = 0; + break; + + case "small": + val = getDefaultFontSize() - 2; + break; + + case "medium": + val = getDefaultFontSize(); + break; + + case "large": + val = getDefaultFontSize() + 2; + break; + + case "smaller": + val = pVal - 2; + break; + + case "bigger": + val = pVal + 2; + break; + + default: + if (isNaN(val)) + val = 0; + else + val = Number(val); + } + // Save the new value. + view.prefs[pref] = val; + } + + // Show the user what the pref is set to. + if (view.prefs[pref] == 0) + display(MSG_FONTS_SIZE_DEFAULT); + else + display(getMsg(MSG_FONTS_SIZE_FMT, view.prefs[pref])); + } + else if (e.command.name == "font-family-other") + { + val = prompt(MSG_FONTS_FAMILY_PICK, view.prefs["font.family"]); + if (!val) + return; + + dispatch("font-family", { font: val }); + } + else if (e.command.name == "font-size-other") + { + pVal = view.prefs["font.size"]; + if (pVal == 0) + pVal = getDefaultFontSize(); + + val = prompt(MSG_FONTS_SIZE_PICK, pVal); + if (!val) + return; + + dispatch("font-size", { fontSize: val }); + } +} + +function cmdDCCChat(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + if (!e.nickname && !e.user) + return display(MSG_DCC_ERR_NOUSER); + + var user; + if (e.nickname) + user = e.server.addUser(e.nickname); + else + user = e.server.addUser(e.user.unicodeName); + + var u = client.dcc.addUser(user); + var c = client.dcc.addChat(u, client.dcc.getNextPort()); + c.request(); + + client.munger.getRule(".inline-buttons").enabled = true; + var cmd = getMsg(MSG_DCC_COMMAND_CANCEL, "dcc-close " + c.id); + display(getMsg(MSG_DCCCHAT_SENT_REQUEST, c._getParams().concat(cmd)), + "DCC-CHAT"); + client.munger.getRule(".inline-buttons").enabled = false; + + return true; +} + +function cmdDCCClose(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + // If there is no nickname specified, use current view. + if (!e.nickname) + { + // Both DCC chat and file transfers can be aborted like this. + if (e.sourceObject.TYPE.substr(0, 6) == "IRCDCC") + { + if (e.sourceObject.isActive()) + return e.sourceObject.abort(); + return true; + } + // ...if there is one. + return display(MSG_DCC_ERR_NOTDCC); + } + + var o = client.dcc.findByID(e.nickname); + if (o) + // Direct ID --> object request. + return o.abort(); + + if (e.type) + e.type = [e.type.toLowerCase()]; + else + e.type = ["chat", "file"]; + + // Go ask the DCC code for some matching requets. + var list = client.dcc.getMatches + (e.nickname, e.file, e.type, [DCC_DIR_GETTING, DCC_DIR_SENDING], + [DCC_STATE_REQUESTED, DCC_STATE_ACCEPTED, DCC_STATE_CONNECTED]); + + // Disconnect if only one match. + if (list.length == 1) + return list[0].abort(); + + // Oops, couldn't figure the user's requets out, so give them some help. + display(getMsg(MSG_DCC_ACCEPTED_MATCHES, [list.length])); + display(MSG_DCC_MATCHES_HELP); + return true; +} + +function cmdDCCSend(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + const DIRSVC_CID = "@mozilla.org/file/directory_service;1"; + const nsIProperties = Components.interfaces.nsIProperties; + + if (!e.nickname && !e.user) + return display(MSG_DCC_ERR_NOUSER); + + // Accept the request passed in... + var file; + if (!e.file) + { + var pickerRv = pickOpen(MSG_DCCFILE_SEND); + if (!pickerRv.ok) + return false; + file = pickerRv.file; + } + else + { + // Wrap in try/catch because nsIFile creation throws a freaking + // error if it doesn't get a FULL path. + try + { + file = nsLocalFile(e.file); + } + catch(ex) + { + // Ok, try user's home directory. + var fl = Components.classes[DIRSVC_CID].getService(nsIProperties); + file = fl.get("Home", Components.interfaces.nsIFile); + + // Another freaking try/catch wrapper. + try + { + // NOTE: This is so pathetic it can't cope with any path + // separators in it, so don't even THINK about lobing a + // relative path at it. + file.append(e.file); + + // Wow. We survived. + } + catch (ex) + { + return display(MSG_DCCFILE_ERR_NOTFOUND); + } + } + } + if (!file.exists()) + return display(MSG_DCCFILE_ERR_NOTFOUND); + if (!file.isFile()) + return display(MSG_DCCFILE_ERR_NOTAFILE); + if (!file.isReadable()) + return display(MSG_DCCFILE_ERR_NOTREADABLE); + + var user; + if (e.nickname) + user = e.server.addUser(e.nickname); + else + user = e.server.addUser(e.user.unicodeName); + + var u = client.dcc.addUser(user); + var c = client.dcc.addFileTransfer(u, client.dcc.getNextPort()); + c.request(file); + + client.munger.getRule(".inline-buttons").enabled = true; + var cmd = getMsg(MSG_DCC_COMMAND_CANCEL, "dcc-close " + c.id); + display(getMsg(MSG_DCCFILE_SENT_REQUEST, [c.user.unicodeName, c.localIP, + c.port, c.filename, + getSISize(c.size), cmd]), + "DCC-FILE"); + client.munger.getRule(".inline-buttons").enabled = false; + + return true; +} + +function cmdDCCList(e) { + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + var counts = { pending: 0, connected: 0, failed: 0 }; + var k; + + // Get all the DCC sessions. + var list = client.dcc.getMatches(); + + for (k = 0; k < list.length; k++) { + var c = list[k]; + var type = c.TYPE.substr(6, c.TYPE.length - 6); + + var dir = MSG_UNKNOWN; + var tf = MSG_UNKNOWN; + if (c.state.dir == DCC_DIR_SENDING) + { + dir = MSG_DCCLIST_DIR_OUT; + tf = MSG_DCCLIST_TO; + } + else if (c.state.dir == DCC_DIR_GETTING) + { + dir = MSG_DCCLIST_DIR_IN; + tf = MSG_DCCLIST_FROM; + } + + var state = MSG_UNKNOWN; + var cmds = ""; + switch (c.state.state) + { + case DCC_STATE_REQUESTED: + state = MSG_DCC_STATE_REQUEST; + if (c.state.dir == DCC_DIR_GETTING) + { + cmds = getMsg(MSG_DCC_COMMAND_ACCEPT, "dcc-accept " + c.id) + " " + + getMsg(MSG_DCC_COMMAND_DECLINE, "dcc-decline " + c.id); + } + else + { + cmds = getMsg(MSG_DCC_COMMAND_CANCEL, "dcc-close " + c.id); + } + counts.pending++; + break; + case DCC_STATE_ACCEPTED: + state = MSG_DCC_STATE_ACCEPT; + counts.connected++; + break; + case DCC_STATE_DECLINED: + state = MSG_DCC_STATE_DECLINE; + break; + case DCC_STATE_CONNECTED: + state = MSG_DCC_STATE_CONNECT; + cmds = getMsg(MSG_DCC_COMMAND_CLOSE, "dcc-close " + c.id); + if (c.TYPE == "IRCDCCFileTransfer") + { + state = getMsg(MSG_DCC_STATE_CONNECTPRO, + [c.progress, + getSISize(c.position), getSISize(c.size), + getSISpeed(c.speed)]); + } + counts.connected++; + break; + case DCC_STATE_DONE: + state = MSG_DCC_STATE_DISCONNECT; + break; + case DCC_STATE_ABORTED: + state = MSG_DCC_STATE_ABORT; + counts.failed++; + break; + case DCC_STATE_FAILED: + state = MSG_DCC_STATE_FAIL; + counts.failed++; + break; + } + client.munger.getRule(".inline-buttons").enabled = true; + display(getMsg(MSG_DCCLIST_LINE, [k + 1, state, dir, type, tf, + c.unicodeName, c.remoteIP, c.port, + cmds])); + client.munger.getRule(".inline-buttons").enabled = false; + } + display(getMsg(MSG_DCCLIST_SUMMARY, [counts.pending, counts.connected, + counts.failed])); + return true; +} + +function cmdDCCAutoAcceptList(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + var list = e.network.prefs["dcc.autoAccept.list"]; + + if (list.length == 0) + display(MSG_DCCACCEPT_DISABLED); + else + display(getMsg(MSG_DCCACCEPT_LIST, arraySpeak(list))); + + return true; +} + +function cmdDCCAutoAcceptAdd(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + var list = e.network.prefs["dcc.autoAccept.list"]; + + if (!e.user && e.server) + e.user = e.server.getUser(e.nickname); + + var mask = e.user ? "*!" + e.user.name + "@" + e.user.host : e.nickname; + var index = arrayIndexOf(list, mask); + if (index == -1) + { + list.push(mask); + list.update(); + display(getMsg(MSG_DCCACCEPT_ADD, mask)); + } + else + { + display(getMsg(MSG_DCCACCEPT_ADDERR, + e.user ? e.user.unicodeName : e.nickname)); + } + return true; +} + +function cmdDCCAutoAcceptDel(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + var list = e.network.prefs["dcc.autoAccept.list"]; + + if (!e.user && e.server) + e.user = e.server.getUser(e.nickname); + + var maskObj, newList = new Array(); + for (var m = 0; m < list.length; ++m) + { + maskObj = getHostmaskParts(list[m]); + if (e.nickname == list[m] || + (e.user && hostmaskMatches(e.user, maskObj, e.server))) + { + display(getMsg(MSG_DCCACCEPT_DEL, list[m])); + } + else + { + newList.push(list[m]); + } + } + + if (list.length > newList.length) + e.network.prefs["dcc.autoAccept.list"] = newList; + else + display(getMsg(MSG_DCCACCEPT_DELERR, + e.user ? e.user.unicodeName : e.nickname)); + + return true; +} + +function cmdDCCAccept(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + function accept(c) + { + if (c.TYPE == "IRCDCCChat") + { + if (!c.accept()) + return false; + + display(getMsg(MSG_DCCCHAT_ACCEPTED, c._getParams()), "DCC-CHAT"); + return true; + } + + // Accept the request passed in... + var filename = c.filename; + var ext = "*"; + var m = filename.match(/...\.([a-z]+)$/i); + if (m) + ext = "*." + m[1]; + + var pickerRv = pickSaveAs(getMsg(MSG_DCCFILE_SAVE_TO, filename), + ["$all", ext], filename); + if (!pickerRv.ok) + return false; + + if (!c.accept(pickerRv.file)) + return false; + + display(getMsg(MSG_DCCFILE_ACCEPTED, c._getParams()), "DCC-FILE"); + return true; + }; + + // If there is no nickname specified, use the "last" item. + // This is the last DCC request that arrvied. + if (!e.nickname && client.dcc.last) + { + if ((new Date() - client.dcc.lastTime) >= 10000) + return accept(client.dcc.last); + return display(MSG_DCC_ERR_ACCEPT_TIME); + } + + var o = client.dcc.findByID(e.nickname); + if (o) + // Direct ID --> object request. + return accept(o); + + if (e.type) + e.type = [e.type.toLowerCase()]; + else + e.type = ["chat", "file"]; + + // Go ask the DCC code for some matching requets. + var list = client.dcc.getMatches(e.nickname, e.file, e.type, + [DCC_DIR_GETTING], [DCC_STATE_REQUESTED]); + // Accept if only one match. + if (list.length == 1) + return accept(list[0]); + + // Oops, couldn't figure the user's request out, so give them some help. + display(getMsg(MSG_DCC_PENDING_MATCHES, [list.length])); + display(MSG_DCC_MATCHES_HELP); + return true; +} + +function cmdDCCDecline(e) +{ + if (!client.prefs["dcc.enabled"]) + return display(MSG_DCC_NOT_ENABLED); + + function decline(c) + { + // Decline the request passed in... + c.decline(); + if (c.TYPE == "IRCDCCChat") + display(getMsg(MSG_DCCCHAT_DECLINED, c._getParams()), "DCC-CHAT"); + else + display(getMsg(MSG_DCCFILE_DECLINED, c._getParams()), "DCC-FILE"); + }; + + // If there is no nickname specified, use the "last" item. + // This is the last DCC request that arrvied. + if (!e.nickname && client.dcc.last) + return decline(client.dcc.last); + + var o = client.dcc.findByID(e.nickname); + if (o) + // Direct ID --> object request. + return decline(o); + + if (!e.type) + e.type = ["chat", "file"]; + + // Go ask the DCC code for some matching requets. + var list = client.dcc.getMatches(e.nickname, e.file, e.type, + [DCC_DIR_GETTING], [DCC_STATE_REQUESTED]); + // Decline if only one match. + if (list.length == 1) + return decline(list[0]); + + // Oops, couldn't figure the user's requets out, so give them some help. + display(getMsg(MSG_DCC_PENDING_MATCHES, [list.length])); + display(MSG_DCC_MATCHES_HELP); + return true; +} + +function cmdDCCShowFile(e) +{ + var f = getFileFromURLSpec(e.file); + if (f) + f = nsLocalFile(f.path); + if (f && f.parent && f.parent.exists()) + { + try + { + f.reveal(); + } + catch (ex) + { + dd(formatException(ex)); + } + } +} + +function cmdTextDirection(e) +{ + var direction; + var sourceObject = getContentDocument(e.sourceObject.frame).body; + + switch (e.dir) + { + case "toggle": + if (sourceObject.getAttribute("dir") == "rtl") + direction = 'ltr'; + else + direction = 'rtl'; + break; + case "rtl": + direction = 'rtl'; + break; + default: + // that is "case "ltr":", + // but even if !e.dir OR e.dir is an invalid value -> set to + // default direction + direction = 'ltr'; + } + client.input.setAttribute("dir", direction); + sourceObject.setAttribute("dir", direction); + + return true; +} + +function cmdInputTextDirection(e) +{ + var direction; + + switch (e.dir) + { + case "rtl": + client.input.setAttribute("dir", "rtl"); + break + default: + // that is "case "ltr":", but even if !e.dir OR e.dir is an + //invalid value -> set to default direction + client.input.setAttribute("dir", "ltr"); + } + + return true; +} + +function cmdInstallPlugin(e) +{ + var ipURL = "chrome://chatzilla/content/install-plugin/install-plugin.xul"; + var ctx = {}; + var pluginDownloader = + { + onStartRequest: function _onStartRequest(request, context) + { + var tempName = "plugin-install.temp"; + if (urlMatches) + tempName += urlMatches[2]; + + ctx.outFile = getTempFile(client.prefs["profilePath"], tempName); + ctx.outFileH = fopen(ctx.outFile, ">"); + }, + onDataAvailable: function _onDataAvailable(request, context, stream, + offset, count) + { + if (!ctx.inputStream) + ctx.inputStream = toSInputStream(stream, true); + + ctx.outFileH.write(ctx.inputStream.readBytes(count)); + }, + onStopRequest: function _onStopRequest(request, context, statusCode) + { + ctx.outFileH.close(); + + if (statusCode == 0) + { + client.installPlugin(e.name, ctx.outFile); + } + else + { + display(getMsg(MSG_INSTALL_PLUGIN_ERR_DOWNLOAD, statusCode), + MT_ERROR); + } + + try + { + ctx.outFile.remove(false); + } + catch (ex) + { + display(getMsg(MSG_INSTALL_PLUGIN_ERR_REMOVE_TEMP, ex), + MT_ERROR); + } + } + }; + + if (!e.url) + { + if ("installPluginDialog" in client) + return client.installPluginDialog.focus(); + + window.openDialog(ipURL, "", "chrome,dialog", client); + return; + } + + var urlMatches = e.url.match(/([^\/]+?)((\..{0,3}){0,2})$/); + if (!e.name) + { + if (urlMatches) + { + e.name = urlMatches[1]; + } + else + { + display(MSG_INSTALL_PLUGIN_ERR_NO_NAME, MT_ERROR); + return; + } + } + + // Do real install here. + switch (e.url.match(/^[^:]+/)[0]) + { + case "file": + client.installPlugin(e.name, e.url); + break; + + case "http": + case "https": + try + { + var channel = Services.io.newChannel( + e.url, "UTF-8", null, null, + Services.scriptSecurityManager.getSystemPrincipal(), null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + display(getMsg(MSG_INSTALL_PLUGIN_DOWNLOADING, e.url), + MT_INFO); + channel.asyncOpen(pluginDownloader, { e: e }); + } + catch (ex) + { + display(getMsg(MSG_INSTALL_PLUGIN_ERR_DOWNLOAD, ex), MT_ERROR); + return; + } + break; + + default: + display(MSG_INSTALL_PLUGIN_ERR_PROTOCOL, MT_ERROR); + } +} + +function cmdUninstallPlugin(e) +{ + if (e.plugin) + { + client.uninstallPlugin(e.plugin); + } +} + +function cmdFind(e) +{ + if (!e.rest) + { + findInPage(getFindData(e)); + return; + } + + // Used from the inputbox, set the search string and find the first + // occurrence using find-again. + const FINDSVC_ID = "@mozilla.org/find/find_service;1"; + var findService = getService(FINDSVC_ID, "nsIFindService"); + // Make sure it searches the entire document, but don't lose the old setting + var oldWrap = findService.wrapFind; + findService.wrapFind = true; + findService.searchString = e.rest; + findAgainInPage(getFindData(e)); + // Restore wrap setting: + findService.wrapFind = oldWrap; +} + +function cmdFindAgain(e) +{ + if (canFindAgainInPage()) + findAgainInPage(getFindData(e)); +} + +function cmdURLs(e) +{ + var urls = client.urlLogger.read().reverse(); + + if (urls.length == 0) + { + display(MSG_URLS_NONE); + } + else + { + /* Temporarily remove the URL logger to avoid changing the list when + * displaying it. + */ + var logger = client.urlLogger; + delete client.urlLogger; + + var num = e.number || client.prefs["urls.display"]; + if (num > urls.length) + num = urls.length; + display(getMsg(MSG_URLS_HEADER, num)); + + for (var i = 0; i < num; i++) + display(getMsg(MSG_URLS_ITEM, [i + 1, urls[i]])); + + client.urlLogger = logger; + } +} + +function cmdWebSearch(e) +{ + let submission = Services.search.currentEngine + .getSubmission(e.selectedText); + let newTabPref = Services.prefs.getBoolPref("browser.search.opentabforcontextsearch"); + dispatch(newTabPref ? "goto-url-newtab" : "goto-url-newwin", + {url: submission.uri.asciiSpec, + shiftKey: e.shiftKey}); +} |