/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is ChatZilla. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert Ginda, , original author * Chiaki Koufugata chiaki@mozilla.gr.jp UI i18n * Samuel Sieb, samuel@sieb.net, MIRC color codes, munger menu, and various * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const __cz_version = "0.9.75.1"; const __cz_condition = "green"; const __cz_suffix = ""; const __cz_guid = "59c81df5-4b7a-477b-912d-4e0fdf64e5f2"; const __cz_locale = "0.9.75"; var warn; var ASSERT; var TEST; if (DEBUG) { _dd_pfx = "cz: "; warn = function (msg) { dumpln ("** WARNING " + msg + " **"); } TEST = ASSERT = function _assert(expr, msg) { if (!expr) { dd("** ASSERTION FAILED: " + msg + " **\n" + getStackTrace() + "\n"); return false; } else { return true; } } } else dd = warn = TEST = ASSERT = function (){}; var client = new Object(); client.TYPE = "IRCClient"; client.COMMAND_CHAR = "/"; client.STEP_TIMEOUT = 500; client.MAX_MESSAGES = 200; client.MAX_HISTORY = 50; /* longest nick to show in display before forcing the message to a block level * element */ client.MAX_NICK_DISPLAY = 14; /* longest word to show in display before abbreviating */ client.MAX_WORD_DISPLAY = 20; client.PRINT_DIRECTION = 1; /*1 => new messages at bottom, -1 => at top */ client.MAX_MSG_PER_ROW = 3; /* default number of messages to collapse into a * single row, max. */ client.INITIAL_COLSPAN = 5; /* MAX_MSG_PER_ROW cannot grow to greater than * one half INITIAL_COLSPAN + 1. */ client.NOTIFY_TIMEOUT = 5 * 60 * 1000; /* update notify list every 5 minutes */ // Check every minute which networks have away statuses that need an update. client.AWAY_TIMEOUT = 60 * 1000; client.SLOPPY_NETWORKS = true; /* true if msgs from a network can be displayed * on the current object if it is related to * the network (ie, /whois results will appear * on the channel you're viewing, if that channel * is on the network that the results came from) */ client.DOUBLETAB_TIME = 500; client.IMAGEDIR = "chrome://chatzilla/skin/images/"; client.HIDE_CODES = true; /* true if you'd prefer to show numeric response * codes as some default value (ie, "===") */ /* true if the browser widget shouldn't be allowed to take focus. windows, and * probably the mac, need to be able to give focus to the browser widget for * copy to work properly. */ client.NO_BROWSER_FOCUS = (navigator.platform.search(/mac|win/i) == -1); client.DEFAULT_RESPONSE_CODE = "==="; /* Minimum number of users above or below the conference limit the user count * must go, before it is changed. This allows the user count to fluctuate * around the limit without continously going on and off. */ client.CONFERENCE_LOW_PASS = 10; client.viewsArray = new Array(); client.activityList = new Object(); client.hostCompat = new Object(); client.inputHistory = new Array(); client.lastHistoryReferenced = -1; client.incompleteLine = ""; client.lastTabUp = new Date(); client.awayMsgs = new Array(); client.awayMsgCount = 5; CIRCNetwork.prototype.INITIAL_CHANNEL = ""; CIRCNetwork.prototype.MAX_MESSAGES = 100; CIRCNetwork.prototype.IGNORE_MOTD = false; CIRCNetwork.prototype.RECLAIM_WAIT = 15000; CIRCNetwork.prototype.RECLAIM_TIMEOUT = 400000; CIRCNetwork.prototype.MIN_RECONNECT_MS = 15 * 1000; // 15s CIRCNetwork.prototype.MAX_RECONNECT_MS = 2 * 60 * 60 * 1000; // 2h CIRCServer.prototype.READ_TIMEOUT = 0; CIRCServer.prototype.PRUNE_OLD_USERS = 0; // prune on user quit. CIRCUser.prototype.MAX_MESSAGES = 200; CIRCChannel.prototype.MAX_MESSAGES = 300; CIRCChanUser.prototype.MAX_MESSAGES = 200; function init() { if (("initialized" in client) && client.initialized) return; client.initialized = false; client.networks = new Object(); client.entities = new Object(); client.eventPump = new CEventPump (200); if (DEBUG) { /* hook all events EXCEPT server.poll and *.event-end types * (the 4th param inverts the match) */ client.debugHook = client.eventPump.addHook([{type: "poll", set:/^(server|dcc-chat)$/}, {type: "event-end"}], event_tracer, "event-tracer", true /* negate */, false /* disable */); } initApplicationCompatibility(); initMessages(); if (client.host == "") showErrorDlg(getMsg(MSG_ERR_UNKNOWN_HOST, client.unknownUID)); initRDF(); initCommands(); initPrefs(); initMunger(); initNetworks(); initMenus(); initStatic(); initHandlers(); // Create DCC handler. client.dcc = new CIRCDCC(client); client.ident = new IdentServer(client); // start logging. nothing should call display() before this point. if (client.prefs["log"]) client.openLogFile(client); // kick-start a log-check interval to make sure we change logfiles in time: // It will fire 2 seconds past the next full hour. setTimeout("checkLogFiles()", 3602000 - (Number(new Date()) % 3600000)); // Make sure the userlist is on the correct side. updateUserlistSide(client.prefs["userlistLeft"]); client.display(MSG_WELCOME, "HELLO"); client.dispatch("set-current-view", { view: client }); importFromFrame("updateHeader"); importFromFrame("setHeaderState"); importFromFrame("changeCSS"); importFromFrame("updateMotifSettings"); importFromFrame("addUsers"); importFromFrame("updateUsers"); importFromFrame("removeUsers"); processStartupScripts(); client.commandManager.installKeys(document); createMenus(); initIcons(); client.busy = false; updateProgress(); client.initialized = true; dispatch("help", { hello: true }); dispatch("networks"); initInstrumentation(); setTimeout(processStartupURLs, 0); } function initStatic() { client.mainWindow = window; try { var io = Components.classes['@mozilla.org/network/io-service;1']; client.iosvc = io.getService(Components.interfaces.nsIIOService); } catch (ex) { dd("IO service failed to initialize: " + ex); } try { const nsISound = Components.interfaces.nsISound; client.sound = Components.classes["@mozilla.org/sound;1"].createInstance(nsISound); client.soundList = new Object(); } catch (ex) { dd("Sound failed to initialize: " + ex); } try { const nsIGlobalHistory = Components.interfaces.nsIGlobalHistory; const GHIST_CONTRACTID = "@mozilla.org/browser/global-history;1"; client.globalHistory = Components.classes[GHIST_CONTRACTID].getService(nsIGlobalHistory); } catch (ex) { dd("Global History failed to initialize: " + ex); } try { const nsISDateFormat = Components.interfaces.nsIScriptableDateFormat; const DTFMT_CID = "@mozilla.org/intl/scriptabledateformat;1"; client.dtFormatter = Components.classes[DTFMT_CID].createInstance(nsISDateFormat); // Mmmm, fun. This ONLY affects the ChatZilla window, don't worry! Date.prototype.toStringInt = Date.prototype.toString; Date.prototype.toString = function() { var dtf = client.dtFormatter; return dtf.FormatDateTime("", dtf.dateFormatLong, dtf.timeFormatSeconds, this.getFullYear(), this.getMonth() + 1, this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds() ); } } catch (ex) { dd("Locale-correct date formatting failed to initialize: " + ex); } multilineInputMode(client.prefs["multiline"]); if (client.prefs["showModeSymbols"]) setListMode("symbol"); else setListMode("graphic"); var tree = document.getElementById('user-list'); tree.setAttribute("ondraggesture", "nsDragAndDrop.startDrag(event, userlistDNDObserver);"); setDebugMode(client.prefs["debugMode"]); var ver = __cz_version + (__cz_suffix ? "-" + __cz_suffix : ""); var ua = navigator.userAgent; var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo"); if (app) { // Use the XUL host app info, and Gecko build ID. if (app.ID == "{" + __cz_guid + "}") { // We ARE the app, in other words, we're running in XULrunner. // Because of this, we must disregard app.(name|vendor|version). // "XULRunner 1.7+/2005071506" ua = "XULRunner " + app.platformVersion + "/" + app.platformBuildID; // "XULRunner 1.7+/2005071506, Windows" CIRCServer.prototype.HOST_RPLY = ua + ", " + client.platform; } else { // "Firefox 1.0+/2005071506" ua = app.name + " " + app.version + "/"; if ("platformBuildID" in app) // 1.1 and up ua += app.platformBuildID; else if ("geckoBuildID" in app) // 1.0 - 1.1 trunk only ua += app.geckoBuildID; else // Uh oh! ua += "??????????"; // "Mozilla Firefox 1.0+, Windows" CIRCServer.prototype.HOST_RPLY = app.vendor + " " + app.name + " " + app.version + ", " + client.platform; } } else { // Extract the revision number, and Gecko build ID. var ary = navigator.userAgent.match(/(rv:[^;)\s]+).*?Gecko\/(\d+)/); if (ary) { if (navigator.vendor) ua = navigator.vendor + " " + navigator.vendorSub; // FF 1.0 else ua = client.entities.brandShortName + " " + ary[1]; // Suite ua = ua + "/" + ary[2]; } CIRCServer.prototype.HOST_RPLY = client.entities.brandShortName + ", " + client.platform; } client.userAgent = getMsg(MSG_VERSION_REPLY, [ver, ua]); CIRCServer.prototype.VERSION_RPLY = client.userAgent; CIRCServer.prototype.SOURCE_RPLY = MSG_SOURCE_REPLY; client.statusBar = new Object(); client.statusBar["server-nick"] = document.getElementById("server-nick"); client.statusElement = document.getElementById("status-text"); client.defaultStatus = MSG_DEFAULT_STATUS; client.progressPanel = document.getElementById("status-progress-panel"); client.progressBar = document.getElementById("status-progress-bar"); client.logFile = null; setInterval("onNotifyTimeout()", client.NOTIFY_TIMEOUT); // Call every minute, will check only the networks necessary. setInterval("onWhoTimeout()", client.AWAY_TIMEOUT); client.awayMsgs = [{ message: MSG_AWAY_DEFAULT }]; var awayFile = new nsLocalFile(client.prefs["profilePath"]); awayFile.append("awayMsgs.txt"); if (awayFile.exists()) { var awayLoader = new TextSerializer(awayFile); if (awayLoader.open("<")) { // Load the first item from the file. var item = awayLoader.deserialize(); if (item instanceof Array) { // If the first item is an array, it is the entire thing. client.awayMsgs = item; } else { /* Not an array, so we have the old format of a single object * per entry. */ client.awayMsgs = [item]; while ((item = awayLoader.deserialize())) client.awayMsgs.push(item); } awayLoader.close(); } } client.defaultCompletion = client.COMMAND_CHAR + "help "; client.deck = document.getElementById('output-deck'); } function initApplicationCompatibility() { // This routine does nothing more than tweak the UI based on the host // application. /* client.hostCompat.typeChromeBrowser indicates whether we should use * type="chrome" elements for the output window documents. * Using these is necessary to work properly with xpcnativewrappers, but * broke selection in older builds. */ client.hostCompat.typeChromeBrowser = false; // Set up simple host and platform information. client.host = "Unknown"; var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo"); if (app) { // Use the XULAppInfo.ID to find out what host we run on. switch (app.ID) { case "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": client.host = "Firefox"; if (compareVersions(app.version, "1.4") <= 0) client.hostCompat.typeChromeBrowser = true; break; case "{" + __cz_guid + "}": // We ARE the app, in other words, we're running in XULrunner. client.host = "XULrunner"; client.hostCompat.typeChromeBrowser = true; break; case "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": // SeaMonkey client.host = "Mozilla"; client.hostCompat.typeChromeBrowser = true; break; case "{a463f10c-3994-11da-9945-000d60ca027b}": // Flock client.host = "Flock"; client.hostCompat.typeChromeBrowser = true; break; default: client.unknownUID = app.ID; client.host = ""; // Unknown host, show an error later. } } else if ("getBrowserURL" in window) { var url = getBrowserURL(); if (url == "chrome://navigator/content/navigator.xul") { client.host = "Mozilla"; } else if (url == "chrome://browser/content/browser.xul") { client.host = "Firefox"; } else { client.host = ""; // We don't know this host. Show an error later. client.unknownUID = url; } } client.platform = "Unknown"; if (navigator.platform.search(/mac/i) > -1) client.platform = "Mac"; if (navigator.platform.search(/win/i) > -1) client.platform = "Windows"; if (navigator.platform.search(/linux/i) > -1) client.platform = "Linux"; if (navigator.platform.search(/os\/2/i) > -1) client.platform = "OS/2"; client.hostPlatform = client.host + client.platform; CIRCServer.prototype.OS_RPLY = navigator.oscpu + " (" + navigator.platform + ")"; // Windows likes \r\n line endings, as wussy-notepad can't cope with just // \n logs. if (client.platform == "Windows") client.lineEnd = "\r\n"; else client.lineEnd = "\n"; } function initIcons() { // Make sure we got the ChatZilla icon(s) in place first. const iconName = "chatzilla-window"; const suffixes = [".ico", ".xpm", "16.xpm"]; /* when installing on Mozilla, the XPI has the power to put the icons where * they are needed - in Firefox, it doesn't. So we move them here, instead. * In XULRunner, things are more fun, as we're not an extension. */ var sourceDir; if ((client.host == "Firefox") || (client.host == "Flock")) { sourceDir = getSpecialDirectory("ProfD"); sourceDir.append("extensions"); sourceDir.append("{" + __cz_guid + "}"); sourceDir.append("defaults"); } else if (client.host == "XULrunner") { sourceDir = getSpecialDirectory("resource:app"); sourceDir.append("chrome"); sourceDir.append("icons"); } else { return; } var destDir = getSpecialDirectory("AChrom"); destDir.append("icons"); destDir.append("default"); if (!destDir.exists()) { try { mkdir(destDir); } catch(ex) { return; } } for (var i = 0; i < suffixes.length; i++) { var iconDest = destDir.clone(); iconDest.append(iconName + suffixes[i]); var iconSrc = sourceDir.clone(); iconSrc.append(iconName + suffixes[i]); if (iconSrc.exists() && !iconDest.exists()) { try { iconSrc.copyTo(iconDest.parent, iconDest.leafName); } catch(ex){} } } } function initInstrumentation() { // Make sure we assign the user a random key - this is not used for // anything except percentage chance of participation. if (client.prefs["instrumentation.key"] == 0) { var rand = 1 + Math.round(Math.random() * 10000); client.prefs["instrumentation.key"] = rand; } runInstrumentation("inst1"); } function runInstrumentation(name, firstRun) { if (!/^inst\d+$/.test(name)) return; // Values: // 0 = not answered question // 1 = allowed inst // 2 = denied inst if (client.prefs["instrumentation." + name] == 0) { // We only want 1% of people to be asked here. if (client.prefs["instrumentation.key"] > 100) return; // User has not seen the info about this system. Show them the info. var cmdYes = "allow-" + name; var cmdNo = "deny-" + name; var btnYes = getMsg(MSG_INST1_COMMAND_YES, cmdYes); var btnNo = getMsg(MSG_INST1_COMMAND_NO, cmdNo); client.munger.entries[".inline-buttons"].enabled = true; client.display(getMsg("msg." + name + ".msg1", [btnYes, btnNo])); client.display(getMsg("msg." + name + ".msg2", [cmdYes, cmdNo])); client.munger.entries[".inline-buttons"].enabled = false; // Don't hide *client* if we're asking the user about the startup ping. client.lockView = true; return; } if (client.prefs["instrumentation." + name] != 1) return; if (name == "inst1") runInstrumentation1(firstRun); } function runInstrumentation1(firstRun) { function inst1onLoad() { if (/OK/.test(req.responseText)) client.display(MSG_INST1_MSGRPLY2); else client.display(getMsg(MSG_INST1_MSGRPLY1, MSG_UNKNOWN)); }; function inst1onError() { client.display(getMsg(MSG_INST1_MSGRPLY1, req.statusText)); }; try { const baseURI = "http://silver.warwickcompsoc.co.uk/" + "mozilla/chatzilla/instrumentation/startup?"; if (firstRun) { // Do a first-run ping here. var frReq = new XMLHttpRequest(); frReq.open("GET", baseURI + "first-run"); frReq.send(null); } var data = new Array(); data.push("ver=" + encodeURIComponent(CIRCServer.prototype.VERSION_RPLY)); data.push("host=" + encodeURIComponent(client.hostPlatform)); data.push("chost=" + encodeURIComponent(CIRCServer.prototype.HOST_RPLY)); data.push("cos=" + encodeURIComponent(CIRCServer.prototype.OS_RPLY)); var url = baseURI + data.join("&"); var req = new XMLHttpRequest(); req.onload = inst1onLoad; req.onerror = inst1onError; req.open("GET", url); req.send(null); } catch (ex) { client.display(getMsg(MSG_INST1_MSGRPLY1, formatException(ex))); } } function getFindData(e) { var findData = new nsFindInstData(); findData.browser = e.sourceObject.frame; findData.rootSearchWindow = e.sourceObject.frame.contentWindow; findData.currentSearchWindow = e.sourceObject.frame.contentWindow; /* Yay, evil hacks! findData.init doesn't care about the findService, it * gets option settings from webBrowserFind. As we want the wrap option *on* * when we use /find foo, we set it on the findService there. However, * restoring the original value afterwards doesn't help, because init() here * overrides that value. Unless we make .init do something else, of course: */ findData._init = findData.init; findData.init = function init() { this._init(); const FINDSVC_ID = "@mozilla.org/find/find_service;1"; var findService = getService(FINDSVC_ID, "nsIFindService"); this.webBrowserFind.wrapFind = findService.wrapFind; }; return findData; } function importFromFrame(method) { client.__defineGetter__(method, import_wrapper); CIRCNetwork.prototype.__defineGetter__(method, import_wrapper); CIRCChannel.prototype.__defineGetter__(method, import_wrapper); CIRCUser.prototype.__defineGetter__(method, import_wrapper); CIRCDCCChat.prototype.__defineGetter__(method, import_wrapper); CIRCDCCFileTransfer.prototype.__defineGetter__(method, import_wrapper); function import_wrapper() { var dummy = function(){}; if (!("frame" in this)) return dummy; try { var window = getContentWindow(this.frame) if (window && "initialized" in window && window.initialized && method in window) { return window[method]; } } catch (ex) { ASSERT(0, "Caught exception calling: " + method + "\n" + ex); } return dummy; }; } function processStartupScripts() { client.plugins = new Array(); var scripts = client.prefs["initialScripts"]; for (var i = 0; i < scripts.length; ++i) { if (scripts[i].search(/^file:|chrome:/i) != 0) { display(getMsg(MSG_ERR_INVALID_SCHEME, scripts[i]), MT_ERROR); continue; } var path = getFileFromURLSpec(scripts[i]); if (!path.exists()) { display(getMsg(MSG_ERR_ITEM_NOT_FOUND, scripts[i]), MT_WARN); continue; } if (path.isDirectory()) loadPluginDirectory(path); else loadLocalFile(path); } } function loadPluginDirectory(localPath, recurse) { if (typeof recurse == "undefined") recurse = 1; var initPath = localPath.clone(); initPath.append("init.js"); if (initPath.exists()) loadLocalFile(initPath); if (recurse < 1) return; var enumer = localPath.directoryEntries; while (enumer.hasMoreElements()) { var entry = enumer.getNext(); entry = entry.QueryInterface(Components.interfaces.nsILocalFile); if (entry.isDirectory()) loadPluginDirectory(entry, recurse - 1); } } function loadLocalFile(localFile) { var url = getURLSpecFromFile(localFile); var glob = new Object(); dispatch("load", {url: url, scope: glob}); } function getPluginById(id) { for (var i = 0; i < client.plugins.length; ++i) { if (client.plugins[i].id == id) return client.plugins[i]; } return null; } function getPluginIndexById(id) { for (var i = 0; i < client.plugins.length; ++i) { if (client.plugins[i].id == id) return i; } return -1; } function getPluginByURL(url) { for (var i = 0; i < client.plugins.length; ++i) { if (client.plugins[i].url == url) return client.plugins[i]; } return null; } function getPluginIndexByURL(url) { for (var i = 0; i < client.plugins.length; ++i) { if (client.plugins[i].url == url) return i; } return -1; } function processStartupURLs() { var wentSomewhere = false; if ("arguments" in window && 0 in window.arguments && typeof window.arguments[0] == "object" && "url" in window.arguments[0]) { var url = window.arguments[0].url; if (url.search(/^ircs?:\/?\/?\/?$/i) == -1) { /* if the url is not irc: irc:/, irc://, or ircs equiv then go to it. */ gotoIRCURL(url); wentSomewhere = true; } } /* check to see whether the URL has been passed via the command line instead. */ else if ("arguments" in window && 0 in window.arguments && typeof window.arguments[0] == "string") { var url = window.arguments[0] var urlMatches = url.match(/^ircs?:\/\/\/?(.*)$/) if (urlMatches) { if (urlMatches[1]) { /* if the url is not "irc://", "irc:///" or an ircs equiv then go to it. */ gotoIRCURL(url); wentSomewhere = true; } } else if (url) { /* URL parameter is not blank, but does not not conform to the irc[s] scheme. */ display(getMsg(MSG_ERR_INVALID_SCHEME, url), MT_ERROR); } } if (!wentSomewhere) { /* if we had nowhere else to go, connect to any default urls */ var ary = client.prefs["initialURLs"]; for (var i = 0; i < ary.length; ++i) { if (ary[i] && ary[i] == "irc:///") { // Clean out "default network" entries, which we don't // support any more; replace with the harmless irc:// URL. ary[i] = "irc://"; client.prefs["initialURLs"].update(); } if (ary[i] && ary[i] != "irc://") gotoIRCURL(ary[i]); } } if (client.viewsArray.length > 1 && !isStartupURL("irc://")) { dispatch("delete-view", {view: client}); } } function destroy() { destroyPrefs(); } function setStatus (str) { client.statusElement.setAttribute ("label", str); return str; } client.__defineSetter__ ("status", setStatus); function getStatus () { return client.statusElement.getAttribute ("label"); } client.__defineGetter__ ("status", getStatus); function isVisible (id) { var e = document.getElementById(id); if (!ASSERT(e,"Bogus id ``" + id + "'' passed to isVisible() **")) return false; return (e.getAttribute ("collapsed") != "true"); } client.getConnectedNetworks = function getConnectedNetworks() { var rv = []; for (var n in client.networks) { if (client.networks[n].isConnected()) rv.push(client.networks[n]); } return rv; } function insertLink (matchText, containerTag, data) { var href; var linkText; var trailing; ary = matchText.match(/([.,?]+)$/); if (ary) { linkText = RegExp.leftContext; trailing = ary[1]; } else { linkText = matchText; } var ary = linkText.match (/^(\w[\w-]+):/); if (ary) { if (!("schemes" in client)) { var pfx = "@mozilla.org/network/protocol;1?name="; var len = pfx.length; client.schemes = new Object(); for (var c in Components.classes) { if (c.indexOf(pfx) == 0) client.schemes[c.substr(len)] = true; } } if (!(ary[1] in client.schemes)) { insertHyphenatedWord(matchText, containerTag); return; } href = linkText; } else { href = "http://" + linkText; } /* This gives callers to the munger control over URLs being logged; the * channel topic munger uses this, as well as the "is important" checker. * If either of |dontLogURLs| or |noStateChange| is present and true, we * don't log. */ if ((!("dontLogURLs" in data) || !data.dontLogURLs) && (!("noStateChange" in data) || !data.noStateChange)) { var max = client.prefs["urls.store.max"]; if (client.prefs["urls.list"].unshift(href) > max) client.prefs["urls.list"].pop(); client.prefs["urls.list"].update(); } var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:a"); anchor.setAttribute ("href", href); anchor.setAttribute ("class", "chatzilla-link"); anchor.setAttribute ("target", "_content"); insertHyphenatedWord (linkText, anchor); containerTag.appendChild (anchor); if (trailing) insertHyphenatedWord (trailing, containerTag); } function insertMailToLink (matchText, containerTag) { var href; if (matchText.indexOf ("mailto:") != 0) href = "mailto:" + matchText; else href = matchText; var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:a"); anchor.setAttribute ("href", href); anchor.setAttribute ("class", "chatzilla-link"); //anchor.setAttribute ("target", "_content"); insertHyphenatedWord (matchText, anchor); containerTag.appendChild (anchor); } function insertChannelLink (matchText, containerTag, eventData) { var bogusChannels = /^#(include|error|define|if|ifdef|else|elsif|endif|\d+)$/i; if (!("network" in eventData) || !eventData.network || matchText.search(bogusChannels) != -1) { containerTag.appendChild(document.createTextNode(matchText)); return; } var encodedMatchText = fromUnicode(matchText, eventData.sourceObject); var anchor = document.createElementNS("http://www.w3.org/1999/xhtml", "html:a"); anchor.setAttribute ("href", eventData.network.getURL() + ecmaEscape(encodedMatchText)); anchor.setAttribute ("class", "chatzilla-link"); insertHyphenatedWord (matchText, anchor); containerTag.appendChild (anchor); } function insertTalkbackLink(matchText, containerTag, eventData) { var anchor = document.createElementNS("http://www.w3.org/1999/xhtml", "html:a"); anchor.setAttribute("href", "http://talkback-public.mozilla.org/" + "search/start.jsp?search=2&type=iid&id=" + matchText); anchor.setAttribute("class", "chatzilla-link"); insertHyphenatedWord(matchText, anchor); containerTag.appendChild(anchor); } function insertBugzillaLink (matchText, containerTag, eventData) { var idOrAlias = matchText.match(/bug\s+#?(\d{3,6}|[^\s,]{1,20})/i)[1]; var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:a"); var bugURL; if (eventData.channel) bugURL = eventData.channel.prefs["bugURL"]; else if (eventData.network) bugURL = eventData.network.prefs["bugURL"]; else bugURL = client.prefs["bugURL"]; anchor.setAttribute ("href", bugURL.replace("%s", idOrAlias)); anchor.setAttribute ("class", "chatzilla-link"); anchor.setAttribute ("target", "_content"); insertHyphenatedWord (matchText, anchor); containerTag.appendChild (anchor); } function insertRheet (matchText, containerTag) { var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:a"); anchor.setAttribute ("href", "http://ftp.mozilla.org/pub/mozilla.org/mozilla/libraries/bonus-tracks/rheet.wav"); anchor.setAttribute ("class", "chatzilla-rheet chatzilla-link"); //anchor.setAttribute ("target", "_content"); insertHyphenatedWord (matchText, anchor); containerTag.appendChild (anchor); } function insertQuote (matchText, containerTag) { if (matchText == "``") containerTag.appendChild(document.createTextNode("\u201c")); else containerTag.appendChild(document.createTextNode("\u201d")); } function insertSmiley(emoticon, containerTag) { var type = "error"; if (emoticon.search(/\>[-^v]?\)/) != -1) type = "face-alien"; else if (emoticon.search(/\>[=:;][-^v]?[(|]/) != -1) type = "face-angry"; else if (emoticon.search(/[=:;][-^v]?[Ss\\\/]/) != -1) type = "face-confused"; else if (emoticon.search(/[B8][-^v]?[)\]]/) != -1) type = "face-cool"; else if (emoticon.search(/[=:;][~'][-^v]?\(/) != -1) type = "face-cry"; else if (emoticon.search(/o[._]O/) != -1) type = "face-dizzy"; else if (emoticon.search(/O[._]o/) != -1) type = "face-dizzy-back"; else if (emoticon.search(/o[._]o|O[._]O/) != -1) type = "face-eek"; else if (emoticon.search(/\>[=:;][-^v]?D/) != -1) type = "face-evil"; else if (emoticon.search(/[=:;][-^v]?DD/) != -1) type = "face-lol"; else if (emoticon.search(/[=:;][-^v]?D/) != -1) type = "face-laugh"; else if (emoticon.search(/\([-^v]?D|[xX][-^v]?D/) != -1) type = "face-rofl"; else if (emoticon.search(/[=:;][-^v]?\|/) != -1) type = "face-normal"; else if (emoticon.search(/[=:;][-^v]?\?/) != -1) type = "face-question"; else if (emoticon.search(/[=:;]"[)\]]/) != -1) type = "face-red"; else if (emoticon.search(/9[._]9/) != -1) type = "face-rolleyes"; else if (emoticon.search(/[=:;][-^v]?[(\[]/) != -1) type = "face-sad"; else if (emoticon.search(/[=:][-^v]?[)\]]/) != -1) type = "face-smile"; else if (emoticon.search(/[=:;][-^v]?[0oO]/) != -1) type = "face-surprised"; else if (emoticon.search(/[=:;][-^v]?[pP]/) != -1) type = "face-tongue"; else if (emoticon.search(/;[-^v]?[)\]]/) != -1) type = "face-wink"; if (type == "error") { // We didn't actually match anything, so it'll be a too-generic match // from the munger RegExp. containerTag.appendChild(document.createTextNode(emoticon)); return; } var span = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:span"); /* create a span to hold the emoticon text */ span.setAttribute ("class", "chatzilla-emote-txt"); span.setAttribute ("type", type); span.appendChild (document.createTextNode (emoticon)); containerTag.appendChild (span); /* create an empty span after the text. this span will have an image added * after it with a chatzilla-emote:after css rule. using * chatzilla-emote-txt:after is not good enough because it does not allow us * to turn off the emoticon text, but keep the image. ie. * chatzilla-emote-txt { display: none; } turns off * chatzilla-emote-txt:after as well.*/ span = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:span"); span.setAttribute ("class", "chatzilla-emote"); span.setAttribute ("type", type); span.setAttribute ("title", emoticon); containerTag.appendChild (span); } function mircChangeColor (colorInfo, containerTag, data) { /* If colors are disabled, the caller doesn't want colors specifically, or * the caller doesn't want any state-changing effects, we drop out. */ if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } var ary = colorInfo.match (/.(\d{1,2}|)(,(\d{1,2})|)/); // Do we have a BG color specified...? if (!arrayHasElementAt(ary, 1) || !ary[1]) { // Oops, no colors. delete data.currFgColor; delete data.currBgColor; return; } var fgColor = String(Number(ary[1]) % 16); if (fgColor.length == 1) data.currFgColor = "0" + fgColor; else data.currFgColor = fgColor; // Do we have a BG color specified...? if (arrayHasElementAt(ary, 3) && ary[3]) { var bgColor = String(Number(ary[3]) % 16); if (bgColor.length == 1) data.currBgColor = "0" + bgColor; else data.currBgColor = bgColor; } data.hasColorInfo = true; } function mircToggleBold (colorInfo, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } if ("isBold" in data) delete data.isBold; else data.isBold = true; data.hasColorInfo = true; } function mircToggleUnder (colorInfo, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } if ("isUnderline" in data) delete data.isUnderline; else data.isUnderline = true; data.hasColorInfo = true; } function mircResetColor (text, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange) || !("hasColorInfo" in data)) { return; } delete data.currFgColor; delete data.currBgColor; delete data.isBold; delete data.isUnder; delete data.hasColorInfo; } function mircReverseColor (text, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } var tempColor = ("currFgColor" in data ? data.currFgColor : ""); if ("currBgColor" in data) data.currFgColor = data.currBgColor; else delete data.currFgColor; if (tempColor) data.currBgColor = tempColor; else delete data.currBgColor; data.hasColorInfo = true; } function showCtrlChar(c, containerTag) { var span = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:span"); span.setAttribute ("class", "chatzilla-control-char"); if (c == "\t") { containerTag.appendChild(document.createTextNode(c)); return; } var ctrlStr = c.charCodeAt(0).toString(16); if (ctrlStr.length < 2) ctrlStr = "0" + ctrlStr; span.appendChild (document.createTextNode ("0x" + ctrlStr)); containerTag.appendChild (span); } function insertHyphenatedWord (longWord, containerTag) { var wordParts = splitLongWord (longWord, client.MAX_WORD_DISPLAY); for (var i = 0; i < wordParts.length; ++i) { containerTag.appendChild (document.createTextNode (wordParts[i])); if (i != wordParts.length) { var wbr = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:wbr"); containerTag.appendChild (wbr); } } } function insertInlineButton(text, containerTag, data) { var ary = text.match(/\[\[([^\]]+)\]\[([^\]]+)\]\[([^\]]+)\]\]/); if (!ary) { containerTag.appendChild(document.createTextNode(text)); return; } var label = ary[1]; var title = ary[2]; var command = ary[3]; var link = document.createElementNS("http://www.w3.org/1999/xhtml", "a"); link.setAttribute("href", "x-cz-command:" + encodeURI(command)); link.setAttribute("title", title); link.setAttribute("class", "chatzilla-link"); link.appendChild(document.createTextNode(label)); containerTag.appendChild(document.createTextNode("[")); containerTag.appendChild(link); containerTag.appendChild(document.createTextNode("]")); } function combineNicks(nickList, max) { if (!max) max = 4; var combinedList = []; for (var i = 0; i < nickList.length; i += max) { count = Math.min(max, nickList.length - i); var nicks = nickList.slice(i, i + count); var str = new String(nicks.join(" ")); str.count = count; combinedList.push(str); } return combinedList; } function updateAllStalkExpressions() { var list = client.prefs["stalkWords"]; for (var name in client.networks) { if ("stalkExpression" in client.networks[name]) updateStalkExpression(client.networks[name], list); } } function updateStalkExpression(network) { function escapeChar(ch) { return "\\" + ch; }; var list = client.prefs["stalkWords"]; var ary = new Array(); ary.push(network.primServ.me.unicodeName.replace(/[^\w\d]/g, escapeChar)); for (var i = 0; i < list.length; ++i) ary.push(list[i].replace(/[^\w\d]/g, escapeChar)); var re; if (client.prefs["stalkWholeWords"]) re = "(^|[\\W\\s])((" + ary.join(")|(") + "))([\\W\\s]|$)"; else re = "(" + ary.join(")|(") + ")"; network.stalkExpression = new RegExp(re, "i"); } function getDefaultFontSize() { const PREF_CTRID = "@mozilla.org/preferences-service;1"; const nsIPrefService = Components.interfaces.nsIPrefService; const nsIPrefBranch = Components.interfaces.nsIPrefBranch; const XHTML_NS = "http://www.w3.org/1999/xhtml"; var prefSvc = Components.classes[PREF_CTRID].getService(nsIPrefService); var prefBranch = prefSvc.getBranch(null); // PX size pref: font.size.variable.x-western var pxSize = 16; try { pxSize = prefBranch.getIntPref("font.size.variable.x-western"); } catch(ex) { } var dpi = 96; try { // Get the DPI the fun way (make Mozilla do the work). var b = document.createElement("box"); b.style.width = "1in"; dpi = window.getComputedStyle(b, null).width.match(/^\d+/); } catch(ex) { try { // Get the DPI the fun way (make Mozilla do the work). b = document.createElementNS("box", XHTML_NS); b.style.width = "1in"; dpi = window.getComputedStyle(b, null).width.match(/^\d+/); } catch(ex) { } } return Math.round((pxSize / dpi) * 72); } function getDefaultContext(cx) { if (!cx) cx = new Object(); /* Use __proto__ here and in all other get*Context so that the command can * tell the difference between getObjectDetails and actual parameters. See * cmdJoin for more details. */ cx.__proto__ = getObjectDetails(client.currentObject); return cx; } function getMessagesContext(cx, element) { if (!cx) cx = new Object(); cx.__proto__ = getObjectDetails(client.currentObject); if (!element) element = document.popupNode; while (element) { switch (element.localName) { case "a": var href = element.getAttribute("href"); cx.url = href; break; case "tr": var nickname = element.getAttribute("msg-user"); if (!nickname) break; // strip out a potential ME! suffix var ary = nickname.match(/(\S+)/); nickname = ary[1]; if (!cx.network) break; // NOTE: nickname is the unicodeName here! if (cx.channel) cx.user = cx.channel.getUser(nickname); else cx.user = cx.network.getUser(nickname); if (cx.user) { cx.nickname = cx.user.unicodeName; cx.canonNick = cx.user.canonicalName; } else { cx.nickname = nickname; } break; } element = element.parentNode; } return cx; } function getTabContext(cx, element) { if (!cx) cx = new Object(); if (!element) element = document.popupNode; while (element) { if (element.localName == "tab") { cx.__proto__ = getObjectDetails(element.view); return cx; } element = element.parentNode; } return cx; } function getUserlistContext(cx) { if (!cx) cx = new Object(); cx.__proto__ = getObjectDetails(client.currentObject); if (!cx.channel) return cx; var user, tree = document.getElementById("user-list"); cx.userList = new Array(); cx.canonNickList = new Array(); cx.nicknameList = getSelectedNicknames(tree); for (var i = 0; i < cx.nicknameList.length; ++i) { user = cx.channel.getUser(cx.nicknameList[i]) cx.userList.push(user); cx.canonNickList.push(user.canonicalName); if (i == 0) { cx.user = user; cx.nickname = user.unicodeName; cx.canonNick = user.canonicalName; } } return cx; } function getSelectedNicknames(tree) { var rv = []; if (!tree || !tree.view || !tree.view.selection) return rv; var rangeCount = tree.view.selection.getRangeCount(); // Loop through the selection ranges. for (var i = 0; i < rangeCount; ++i) { var start = {}, end = {}; tree.view.selection.getRangeAt(i, start, end); // If they == -1, we've got no selection, so bail. if ((start.value == -1) && (end.value == -1)) continue; /* Workaround: Because we use select(-1) instead of clearSelection() * (see bug 197667) the tree will then give us selection ranges * starting from -1 instead of 0! (See bug 319066.) */ if (start.value == -1) start.value = 0; // Loop through the contents of the current selection range. for (var k = start.value; k <= end.value; ++k) { var item = tree.contentView.getItemAtIndex(k).firstChild.firstChild; var userName = item.getAttribute("unicodeName"); rv.push(userName); } } return rv; } function setSelectedNicknames(tree, nicknameAry) { if (!tree || !tree.view || !tree.view.selection || !nicknameAry) return; var item, unicodeName, resultAry = []; // Clear selection: tree.view.selection.select(-1); // Loop through the tree to (re-)select nicknames for (var i = 0; i < tree.view.rowCount; i++) { item = tree.contentView.getItemAtIndex(i).firstChild.firstChild; unicodeName = item.getAttribute("unicodeName"); if ((unicodeName != "") && arrayContains(nicknameAry, unicodeName)) { tree.view.selection.toggleSelect(i); resultAry.push(unicodeName); } } // Make sure we pass back a correct array: nicknameAry.length = 0; for (var j = 0; j < resultAry.length; j++) nicknameAry.push(resultAry[j]); } function getFontContext(cx) { if (!cx) cx = new Object(); cx.__proto__ = getObjectDetails(client.currentObject); cx.fontSizeDefault = getDefaultFontSize(); var view = client; if ("prefs" in cx.sourceObject) { cx.fontFamily = view.prefs["font.family"]; if (cx.fontFamily.match(/^(default|(sans-)?serif|monospace)$/)) delete cx.fontFamily; cx.fontSize = view.prefs["font.size"]; if (cx.fontSize == 0) delete cx.fontSize; } return cx; } function msgIsImportant (msg, sourceNick, network) { /* This is a huge hack, but it works. What we want is to match against the * plain text of a message, ignoring color codes, bold, etc. so we put it * through the munger. This produces a tree of HTML elements, which we use * |.innerHTML| to convert to a textual representation. * * Then we remove all the HTML tags, using a RegExp. * * It certainly isn't ideal, and there has to be a better way, but it: * a) works, and * b) is fast enough to not cause problems, * so it will do for now. * * Note also that we don't want to log URLs munged here, or generally do * any state-changing stuff. */ var plainMsg = client.munger.munge(msg, null, { noStateChange: true }); plainMsg = plainMsg.innerHTML.replace(/<[^>]+>/g, ""); var re = network.stalkExpression; if (plainMsg.search(re) != -1 || sourceNick && sourceNick.search(re) == 0) return true; return false; } function isStartupURL(url) { return arrayContains(client.prefs["initialURLs"], url); } function cycleView (amount) { var len = client.viewsArray.length; if (len <= 1) return; var tb = getTabForObject (client.currentObject); if (!tb) return; var vk = Number(tb.getAttribute("viewKey")); var destKey = (vk + amount) % len; /* wrap around */ if (destKey < 0) destKey += len; dispatch("set-current-view", { view: client.viewsArray[destKey].source }); } // Plays the sound for a particular event on a type of object. function playEventSounds(type, event) { if (!client.sound || !client.prefs["sound.enabled"]) return; // Converts .TYPE values into the event object names. // IRCChannel => channel, IRCUser => user, etc. if (type.match(/^IRC/)) type = type.substr(3, type.length).toLowerCase(); var ev = type + "." + event; if (ev in client.soundList) return; if (!(("sound." + ev) in client.prefs)) return; var s = client.prefs["sound." + ev]; if (!s) return; if (client.prefs["sound.overlapDelay"] > 0) { client.soundList[ev] = true; setTimeout("delete client.soundList['" + ev + "']", client.prefs["sound.overlapDelay"]); } if (event == "start") { blockEventSounds(type, "event"); blockEventSounds(type, "chat"); blockEventSounds(type, "stalk"); } playSounds(s); } // Blocks a particular type of event sound occuring. function blockEventSounds(type, event) { if (!client.sound || !client.prefs["sound.enabled"]) return; // Converts .TYPE values into the event object names. // IRCChannel => channel, IRCUser => user, etc. if (type.match(/^IRC/)) type = type.substr(3, type.length).toLowerCase(); var ev = type + "." + event; if (client.prefs["sound.overlapDelay"] > 0) { client.soundList[ev] = true; setTimeout("delete client.soundList['" + ev + "']", client.prefs["sound.overlapDelay"]); } } function playSounds(list) { var ary = list.split (" "); if (ary.length == 0) return; playSound(ary[0]); for (var i = 1; i < ary.length; ++i) setTimeout(playSound, 250 * i, ary[i]); } function playSound(file) { if (!client.sound || !client.prefs["sound.enabled"] || !file) return; if (file == "beep") { client.sound.beep(); } else { try { var uri = client.iosvc.newURI(file, null, null); client.sound.play(uri); } catch (ex) { // ignore exceptions from this pile of code. } } } /* timer-based mainloop */ function mainStep() { try { var count = client.eventPump.stepEvents(); if (count > 0) setTimeout("mainStep()", client.STEP_TIMEOUT); else setTimeout("mainStep()", client.STEP_TIMEOUT / 5); } catch(ex) { dd("Exception in mainStep!"); dd(formatException(ex)); setTimeout("mainStep()", client.STEP_TIMEOUT); } } function openQueryTab(server, nick) { var user = server.addUser(nick); if (client.globalHistory) client.globalHistory.addPage(user.getURL()); if (!("messages" in user)) { var value = ""; var same = true; for (var c in server.channels) { var chan = server.channels[c]; if (!(user.canonicalName in chan.users)) continue; /* This takes a boolean value for each channel (true - channel has * same value as first), and &&-s them all together. Thus, |same| * will tell us, at the end, if all the channels found have the * same value for charset. */ if (value) same = same && (value == chan.prefs["charset"]); else value = chan.prefs["charset"]; } /* If we've got a value, and it's the same accross all channels, * we use it as the *default* for the charset pref. If not, it'll * just keep the "defer" default which pulls it off the network. */ if (value && same) { user.prefManager.prefRecords["charset"].defaultValue = value; } user.displayHere (getMsg(MSG_QUERY_OPENED, user.unicodeName)); } user.whois(); return user; } function arraySpeak (ary, single, plural) { var rv = ""; var and = MSG_AND; switch (ary.length) { case 0: break; case 1: rv = ary[0]; if (single) rv += " " + single; break; case 2: rv = ary[0] + " " + and + " " + ary[1]; if (plural) rv += " " + plural; break; default: for (var i = 0; i < ary.length - 1; ++i) rv += ary[i] + ", "; rv += and + " " + ary[ary.length - 1]; if (plural) rv += " " + plural; break; } return rv; } function getObjectDetails (obj, rv) { if (!rv) rv = new Object(); if (!ASSERT(obj && typeof obj == "object", "INVALID OBJECT passed to getObjectDetails (" + obj + "). **")) { return rv; } rv.sourceObject = obj; rv.TYPE = obj.TYPE; rv.parent = ("parent" in obj) ? obj.parent : null; rv.user = null; rv.channel = null; rv.server = null; rv.network = null; switch (obj.TYPE) { case "IRCChannel": rv.viewType = MSG_CHANNEL; rv.channel = obj; rv.channelName = obj.unicodeName; rv.server = rv.channel.parent; rv.network = rv.server.parent; break; case "IRCUser": rv.viewType = MSG_USER; rv.user = obj; rv.userName = obj.unicodeName; rv.server = rv.user.parent; rv.network = rv.server.parent; break; case "IRCChanUser": rv.viewType = MSG_USER; rv.user = obj; rv.userName = obj.unicodeName; rv.channel = rv.user.parent; rv.server = rv.channel.parent; rv.network = rv.server.parent; break; case "IRCNetwork": rv.network = obj; rv.viewType = MSG_NETWORK; if ("primServ" in rv.network) rv.server = rv.network.primServ; else rv.server = null; break; case "IRCClient": rv.viewType = MSG_TAB; break; case "IRCDCCUser": //rv.viewType = MSG_USER; rv.user = obj; rv.userName = obj.unicodeName; break; case "IRCDCCChat": //rv.viewType = MSG_USER; rv.chat = obj; rv.user = obj.user; rv.userName = obj.unicodeName; break; case "IRCDCCFileTransfer": //rv.viewType = MSG_USER; rv.file = obj; rv.user = obj.user; rv.userName = obj.unicodeName; rv.fileName = obj.filename; break; default: /* no setup for unknown object */ break; } if (rv.network) rv.networkName = rv.network.unicodeName; return rv; } function findDynamicRule (selector) { var rules = frames[0].document.styleSheets[1].cssRules; if (selector instanceof RegExp) fun = "search"; else fun = "indexOf"; for (var i = 0; i < rules.length; ++i) { var rule = rules.item(i); if (rule.selectorText && rule.selectorText[fun](selector) == 0) return {sheet: frames[0].document.styleSheets[1], rule: rule, index: i}; } return null; } function addDynamicRule (rule) { var rules = frames[0].document.styleSheets[1]; var pos = rules.cssRules.length; rules.insertRule (rule, pos); } function getCommandEnabled(command) { try { var dispatcher = document.commandDispatcher; var controller = dispatcher.getControllerForCommand(command); return controller.isCommandEnabled(command); } catch (e) { return false; } } function doCommand(command) { try { var dispatcher = document.commandDispatcher; var controller = dispatcher.getControllerForCommand(command); if (controller && controller.isCommandEnabled(command)) controller.doCommand(command); } catch (e) { } } function doCommandWithParams(command, params) { try { var dispatcher = document.commandDispatcher; var controller = dispatcher.getControllerForCommand(command); controller.QueryInterface(Components.interfaces.nsICommandController); if (!controller || !controller.isCommandEnabled(command)) return; var cmdparams = newObject("@mozilla.org/embedcomp/command-params;1", "nsICommandParams"); for (var i in params) cmdparams.setISupportsValue(i, params[i]); controller.doCommandWithParams(command, cmdparams); } catch (e) { } } var testURLs = ["irc:", "irc://", "irc:///", "irc:///help", "irc:///help,needkey", "irc://irc.foo.org", "irc://foo:6666", "irc://foo", "irc://irc.foo.org/", "irc://foo:6666/", "irc://foo/", "irc://irc.foo.org/,needpass", "irc://foo/,isserver", "irc://moznet/,isserver", "irc://moznet/", "irc://foo/chatzilla", "irc://foo/chatzilla/", "irc://irc.foo.org/?msg=hello%20there", "irc://irc.foo.org/?msg=hello%20there&ignorethis", "irc://irc.foo.org/%23mozilla,needkey?msg=hello%20there&ignorethis", "invalids", "irc://irc.foo.org/,isnick"]; function doURLTest() { for (var u in testURLs) { dd ("testing url \"" + testURLs[u] + "\""); var o = parseIRCURL(testURLs[u]); if (!o) dd ("PARSE FAILED!"); else dd (dumpObjectTree(o)); dd ("---"); } } function parseIRCURL (url) { var specifiedHost = ""; var rv = new Object(); rv.spec = url; rv.scheme = url.split(":")[0]; rv.host = null; rv.target = ""; rv.port = (rv.scheme == "ircs" ? 9999 : 6667); rv.msg = ""; rv.pass = null; rv.key = null; rv.charset = null; rv.needpass = false; rv.needkey = false; rv.isnick = false; rv.isserver = false; if (url.search(/^(ircs?:\/?\/?)$/i) != -1) return rv; /* split url into / pieces */ var ary = url.match (/^ircs?:\/\/([^\/\s]+)?(\/[^\s]*)?$/i); if (!ary || !ary[1]) { dd ("parseIRCURL: initial split failed"); return null; } var host = ary[1]; var rest = arrayHasElementAt(ary, 2) ? ary[2] : ""; /* split into server (or network) / port */ ary = host.match (/^([^\:]+)?(\:\d+)?$/); if (!ary) { dd ("parseIRCURL: host/port split failed"); return null; } if (arrayHasElementAt(ary, 2)) { if (!arrayHasElementAt(ary, 2)) { dd ("parseIRCURL: port with no host"); return null; } specifiedHost = rv.host = ary[1].toLowerCase(); rv.isserver = true; rv.port = parseInt(ary[2].substr(1)); } else if (arrayHasElementAt(ary, 1)) { specifiedHost = rv.host = ary[1].toLowerCase(); if (specifiedHost.indexOf(".") != -1) rv.isserver = true; } if (rest) { ary = rest.match (/^\/([^\?\s\/,]*)?\/?(,[^\?]*)?(\?.*)?$/); if (!ary) { dd ("parseIRCURL: rest split failed ``" + rest + "''"); return null; } rv.target = arrayHasElementAt(ary, 1) ? ecmaUnescape(ary[1]) : ""; if (rv.target.search(/[\x07,:\s]/) != -1) { dd ("parseIRCURL: invalid characters in channel name"); return null; } var params = arrayHasElementAt(ary, 2) ? ary[2].toLowerCase() : ""; var query = arrayHasElementAt(ary, 3) ? ary[3] : ""; if (params) { rv.isnick = (params.search (/,isnick(?:,|$)/) != -1); if (rv.isnick && !rv.target) { dd ("parseIRCURL: isnick w/o target"); /* isnick w/o a target is bogus */ return null; } if (!rv.isserver) { rv.isserver = (params.search (/,isserver(?:,|$)/) != -1); } if (rv.isserver && !specifiedHost) { dd ("parseIRCURL: isserver w/o host"); /* isserver w/o a host is bogus */ return null; } rv.needpass = (params.search (/,needpass(?:,|$)/) != -1); rv.needkey = (params.search (/,needkey(?:,|$)/) != -1); } if (query) { ary = query.substr(1).split("&"); while (ary.length) { var arg = ary.pop().split("="); /* * we don't want to accept *any* query, or folks could * say things like "target=foo", and overwrite what we've * already parsed, so we only use query args we know about. */ switch (arg[0].toLowerCase()) { case "msg": rv.msg = unescape(arg[1]).replace ("\n", "\\n"); break; case "pass": rv.needpass = true; rv.pass = unescape(arg[1]).replace ("\n", "\\n"); break; case "key": rv.needkey = true; rv.key = unescape(arg[1]).replace ("\n", "\\n"); break; case "charset": rv.charset = unescape(arg[1]).replace ("\n", "\\n"); break; } } } } return rv; } function gotoIRCURL (url) { var urlspec = url; if (typeof url == "string") url = parseIRCURL(url); if (!url) { window.alert (getMsg(MSG_ERR_BAD_IRCURL, urlspec)); return; } if (!url.host) { /* focus the *client* view for irc:, irc:/, and irc:// (the only irc * urls that don't have a host. (irc:/// implies a connect to the * default network.) */ dispatch("client"); return; } var network; if (url.isserver) { var alreadyThere = false; for (var n in client.networks) { if ((client.networks[n].isConnected()) && (client.networks[n].primServ.hostname == url.host) && (client.networks[n].primServ.port == url.port)) { /* already connected to this server/port */ network = client.networks[n]; alreadyThere = true; break; } } if (!alreadyThere) { /* dd ("gotoIRCURL: not already connected to " + "server " + url.host + " trying to connect..."); */ var pass = ""; if (url.needpass) { if (url.pass) pass = url.pass; else pass = promptPassword(getMsg(MSG_HOST_PASSWORD, url.host)); } network = dispatch((url.scheme == "ircs" ? "sslserver" : "server"), {hostname: url.host, port: url.port, password: pass}); if (!url.target) return; if (!("pendingURLs" in network)) network.pendingURLs = new Array(); network.pendingURLs.unshift(url); return; } } else { /* parsed as a network name */ if (!(url.host in client.networks)) { display(getMsg(MSG_ERR_UNKNOWN_NETWORK, url.host)); return; } network = client.networks[url.host]; if (!network.isConnected()) { /* dd ("gotoIRCURL: not already connected to " + "network " + url.host + " trying to connect..."); */ client.connectToNetwork(network, (url.scheme == "ircs" ? true : false)); if (!url.target) return; if (!("pendingURLs" in network)) network.pendingURLs = new Array(); network.pendingURLs.unshift(url); return; } } /* already connected, do whatever comes next in the url */ //dd ("gotoIRCURL: connected, time to finish parsing ``" + url + "''"); if (url.target) { var targetObject; var ev; if (url.isnick) { /* url points to a person. */ var nick = url.target; var ary = url.target.split("!"); if (ary) nick = ary[0]; targetObject = network.dispatch("query", {nickname: nick}); } else { /* url points to a channel */ var key; if (url.needkey) { if (url.key) key = url.key; else key = window.promptPassword(getMsg(MSG_URL_KEY, url.spec)); } if (url.charset) { var d = { channelName: url.target, key: key, charset: url.charset }; targetObject = network.dispatch("join", d); } else { // Must do this the hard way... we have the server's format // for the channel name here, and all our commands only work // with the Unicode forms. var serv = network.primServ; var target = url.target; /* If we don't have a valid prefix, stick a "#" on it. * NOTE: This is always a "#" so that URLs may be compared * properly without involving the server (e.g. off-line). */ if ((arrayIndexOf(["#", "&", "+", "!"], target[0]) == -1) && (arrayIndexOf(serv.channelTypes, target[0]) == -1)) { target = "#" + target; } var chan = new CIRCChannel(serv, null, target); d = { channelName: chan.unicodeName, key: key, charset: url.charset }; targetObject = network.dispatch("join", d); } if (!targetObject) return; } if (url.msg) { var msg; if (url.msg.indexOf("\01ACTION") == 0) { msg = filterOutput(url.msg, "ACTION", targetObject); targetObject.display(msg, "ACTION", "ME!", client.currentObject); } else { msg = filterOutput(url.msg, "PRIVMSG", targetObject); targetObject.display(msg, "PRIVMSG", "ME!", client.currentObject); } targetObject.say(msg); dispatch("set-current-view", { view: targetObject }); } } else { if (!network.messages) network.displayHere (getMsg(MSG_NETWORK_OPENED, network.unicodeName)); dispatch("set-current-view", { view: network }); } } function updateProgress() { var busy; var progress = -1; if ("busy" in client.currentObject) busy = client.currentObject.busy; if ("progress" in client.currentObject) progress = client.currentObject.progress; if (!busy) progress = 0; client.progressPanel.collapsed = !busy; client.progressBar.mode = (progress < 0 ? "undetermined" : "determined"); if (progress >= 0) client.progressBar.value = progress; } function updateSecurityIcon() { var o = getObjectDetails(client.currentObject); var securityButton = window.document.getElementById("security-button"); securityButton.firstChild.value = ""; securityButton.removeAttribute("level"); securityButton.removeAttribute("tooltiptext"); if (!o.server || !o.server.isConnected) // No server or connection? { securityButton.setAttribute("tooltiptext", MSG_SECURITY_INFO); return; } var securityState = o.server.connection.getSecurityState() switch (securityState[0]) { case STATE_IS_SECURE: securityButton.firstChild.value = o.server.hostname; if (securityState[1] == STATE_SECURE_HIGH) securityButton.setAttribute("level", "high"); else // Because low security is the worst we have when being secure securityButton.setAttribute("level", "low"); // Add the tooltip: var issuer = o.server.connection.getCertificate().issuerOrganization; var tooltiptext = getMsg(MSG_SECURE_CONNECTION, issuer); securityButton.setAttribute("tooltiptext", tooltiptext); securityButton.firstChild.setAttribute("tooltiptext", tooltiptext); securityButton.lastChild.setAttribute("tooltiptext", tooltiptext); break; case STATE_IS_BROKEN: securityButton.setAttribute("level", "broken"); // No break to make sure we get the correct tooltip case STATE_IS_INSECURE: default: securityButton.setAttribute("tooltiptext", MSG_SECURITY_INFO); } } function updateNetwork() { var o = getObjectDetails (client.currentObject); var lag = MSG_UNKNOWN; var nick = ""; if (o.server) { if (o.server.me) nick = o.server.me.unicodeName; lag = (o.server.lag != -1) ? o.server.lag : MSG_UNKNOWN; } client.statusBar["header-url"].setAttribute("value", client.currentObject.getURL()); client.statusBar["header-url"].setAttribute("href", client.currentObject.getURL()); client.statusBar["header-url"].setAttribute("name", client.currentObject.unicodeName); } function updateTitle (obj) { if (!(("currentObject" in client) && client.currentObject) || (obj && obj != client.currentObject)) return; var tstring = MSG_TITLE_UNKNOWN; var o = getObjectDetails(client.currentObject); var net = o.network ? o.network.unicodeName : ""; var nick = ""; client.statusBar["server-nick"].disabled = false; switch (client.currentObject.TYPE) { case "IRCNetwork": var serv = "", port = ""; if (client.currentObject.isConnected()) { serv = o.server.hostname; port = o.server.port; if (o.server.me) nick = o.server.me.unicodeName; tstring = getMsg(MSG_TITLE_NET_ON, [nick, net, serv, port]); } else { nick = client.currentObject.INITIAL_NICK; tstring = getMsg(MSG_TITLE_NET_OFF, [nick, net]); } break; case "IRCChannel": var chan = "", mode = "", topic = ""; if ("me" in o.parent) { nick = o.parent.me.unicodeName; if (o.parent.me.canonicalName in client.currentObject.users) { var cuser = client.currentObject.users[o.parent.me.canonicalName]; if (cuser.isFounder) nick = "~" + nick; else if (cuser.isAdmin) nick = "&" + nick; else if (cuser.isOp) nick = "@" + nick; else if (cuser.isHalfOp) nick = "%" + nick; else if (cuser.isVoice) nick = "+" + nick; } } else { nick = MSG_TITLE_NONICK; } chan = o.channel.unicodeName; mode = o.channel.mode.getModeStr(); if (!mode) mode = MSG_TITLE_NO_MODE; topic = o.channel.topic ? o.channel.topic : MSG_TITLE_NO_TOPIC; var re = /\x1f|\x02|\x0f|\x16|\x03([0-9]{1,2}(,[0-9]{1,2})?)?/g; topic = topic.replace(re, ""); tstring = getMsg(MSG_TITLE_CHANNEL, [nick, chan, mode, topic]); break; case "IRCUser": nick = client.currentObject.unicodeName; var source = ""; if (client.currentObject.name) { source = "<" + client.currentObject.name + "@" + client.currentObject.host +">"; } tstring = getMsg(MSG_TITLE_USER, [nick, source]); nick = "me" in o.parent ? o.parent.me.unicodeName : MSG_TITLE_NONICK; break; case "IRCClient": nick = client.prefs["nickname"]; break; case "IRCDCCChat": client.statusBar["server-nick"].disabled = true; nick = o.chat.me.unicodeName; tstring = getMsg(MSG_TITLE_DCCCHAT, o.userName); break; case "IRCDCCFileTransfer": client.statusBar["server-nick"].disabled = true; nick = o.file.me.unicodeName; var data = [o.file.progress, o.file.filename, o.userName]; if (o.file.state.dir == 1) tstring = getMsg(MSG_TITLE_DCCFILE_SEND, data); else tstring = getMsg(MSG_TITLE_DCCFILE_GET, data); break; } if (0 && !client.uiState["tabstrip"]) { var actl = new Array(); for (var i in client.activityList) actl.push ((client.activityList[i] == "!") ? (Number(i) + 1) + "!" : (Number(i) + 1)); if (actl.length > 0) tstring = getMsg(MSG_TITLE_ACTIVITY, [tstring, actl.join (MSG_COMMASP)]); } document.title = tstring; client.statusBar["server-nick"].setAttribute("label", nick); } // Where 'right' is orientation, not wrong/right: function updateUserlistSide(shouldBeLeft) { var listParent = document.getElementById("tabpanels-contents-box"); var isLeft = (listParent.childNodes[0].id == "user-list-box"); if (isLeft == shouldBeLeft) return; if (shouldBeLeft) // Move from right to left. { listParent.insertBefore(listParent.childNodes[1], listParent.childNodes[0]); listParent.insertBefore(listParent.childNodes[2], listParent.childNodes[0]); listParent.childNodes[1].setAttribute("collapse", "before"); } else // Move from left to right. { listParent.appendChild(listParent.childNodes[1]); listParent.appendChild(listParent.childNodes[0]); listParent.childNodes[1].setAttribute("collapse", "after"); } } function multilineInputMode (state) { var multiInput = document.getElementById("multiline-input"); var multiInputBox = document.getElementById("multiline-box"); var singleInput = document.getElementById("input"); var singleInputBox = document.getElementById("singleline-box"); var splitter = document.getElementById("input-splitter"); var iw = document.getElementById("input-widgets"); var h; client._mlMode = state; if (state) /* turn on multiline input mode */ { h = iw.getAttribute ("lastHeight"); if (h) iw.setAttribute ("height", h); /* restore the slider position */ singleInputBox.setAttribute ("collapsed", "true"); splitter.setAttribute ("collapsed", "false"); multiInputBox.setAttribute ("collapsed", "false"); // multiInput should have the same direction as singleInput multiInput.setAttribute("dir", singleInput.getAttribute("dir")); multiInput.value = (client.input ? client.input.value : ""); client.input = multiInput; } else /* turn off multiline input mode */ { h = iw.getAttribute ("height"); iw.setAttribute ("lastHeight", h); /* save the slider position */ iw.removeAttribute ("height"); /* let the slider drop */ splitter.setAttribute ("collapsed", "true"); multiInputBox.setAttribute ("collapsed", "true"); singleInputBox.setAttribute ("collapsed", "false"); // singleInput should have the same direction as multiInput singleInput.setAttribute("dir", multiInput.getAttribute("dir")); singleInput.value = (client.input ? client.input.value : ""); client.input = singleInput; } client.input.focus(); } function displayCertificateInfo() { var o = getObjectDetails(client.currentObject); if (!o.server) return; if (!o.server.isSecure) { alert(getMsg(MSG_INSECURE_SERVER, o.server.hostname)); return; } viewCert(o.server.connection.getCertificate()); } function newInlineText (data, className, tagName) { if (typeof tagName == "undefined") tagName = "html:span"; var a = document.createElementNS ("http://www.w3.org/1999/xhtml", tagName); if (className) a.setAttribute ("class", className); switch (typeof data) { case "string": a.appendChild (document.createTextNode (data)); break; case "object": for (var p in data) if (p != "data") a.setAttribute (p, data[p]); else a.appendChild (document.createTextNode (data[p])); break; case "undefined": break; default: ASSERT(0, "INVALID TYPE ('" + typeof data + "') passed to " + "newInlineText."); break; } return a; } function stringToMsg (message, obj) { var ary = message.split ("\n"); var span = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:span"); var data = getObjectDetails(obj); if (ary.length == 1) client.munger.munge(ary[0], span, data); else { for (var l = 0; l < ary.length - 1; ++l) { client.munger.munge(ary[l], span, data); span.appendChild (document.createElementNS ("http://www.w3.org/1999/xhtml", "html:br")); } client.munger.munge(ary[l], span, data); } return span; } function getFrame() { if (client.deck.childNodes.length == 0) return undefined; var panel = client.deck.selectedPanel; return getContentWindow(panel); } client.__defineGetter__ ("currentFrame", getFrame); function setCurrentObject (obj) { function clearList() { client.rdf.Unassert (client.rdf.resNullChan, client.rdf.resChanUser, client.rdf.resNullUser, true); }; if (!ASSERT(obj.messages, "INVALID OBJECT passed to setCurrentObject **")) return; if ("currentObject" in client && client.currentObject == obj) return; var tb, userList; userList = document.getElementById("user-list"); if ("currentObject" in client && client.currentObject) { var co = client.currentObject; // Save any nicknames selected if (client.currentObject.TYPE == "IRCChannel") co.userlistSelection = getSelectedNicknames(userList); tb = getTabForObject(co); } if (tb) { tb.selected = false; tb.setAttribute ("state", "normal"); } /* Unselect currently selected users. * If the splitter's collapsed, the userlist *isn't* visible, but we'll not * get told when it becomes visible, so update it even if it's only the * splitter visible. */ if (isVisible("user-list-box") || isVisible("main-splitter")) { /* Remove currently selected items before this tree gets rerooted, * because it seems to remember the selections for eternity if not. */ if (userList.view && userList.view.selection) userList.view.selection.select(-1); if (obj.TYPE == "IRCChannel") { client.rdf.setTreeRoot("user-list", obj.getGraphResource()); reSortUserlist(userList); // Restore any selections previously made if (("userlistSelection" in obj) && obj.userlistSelection) setSelectedNicknames(userList, obj.userlistSelection); } else { var rdf = client.rdf; rdf.setTreeRoot("user-list", rdf.resNullChan); rdf.Assert (rdf.resNullChan, rdf.resChanUser, rdf.resNullUser, true); setTimeout(clearList, 100); } } client.currentObject = obj; tb = dispatch("create-tab-for-view", { view: obj }); if (tb) { tb.selected = true; tb.setAttribute ("state", "current"); } var vk = Number(tb.getAttribute("viewKey")); delete client.activityList[vk]; client.deck.selectedIndex = vk; updateTitle(); updateProgress(); updateSecurityIcon(); if (client.PRINT_DIRECTION == 1) scrollDown(obj.frame, false); // Input area should have the same direction as the output area if (("frame" in client.currentObject) && client.currentObject.frame && ("contentDocument" in client.currentObject.frame) && client.currentObject.frame.contentDocument && ("body" in client.currentObject.frame.contentDocument) && client.currentObject.frame.contentDocument.body) { var contentArea = client.currentObject.frame.contentDocument.body; client.input.setAttribute("dir", contentArea.getAttribute("dir")); } client.input.focus(); } function checkScroll(frame) { var window = getContentWindow(frame); if (!window || !("document" in window)) return false; return (window.document.height - window.innerHeight - window.pageYOffset) < 160; } function scrollDown(frame, force) { var window = getContentWindow(frame); if (window && (force || checkScroll(frame))) window.scrollTo(0, window.document.height); } /* valid values for |what| are "superfluous", "activity", and "attention". * final value for state is dependant on priority of the current state, and the * new state. the priority is: normal < superfluous < activity < attention. */ function setTabState(source, what, callback) { if (typeof source != "object") { if (!ASSERT(source in client.viewsArray, "INVALID SOURCE passed to setTabState")) return; source = client.viewsArray[source].source; } callback = callback || false; var tb = source.dispatch("create-tab-for-view", { view: source }); var vk = Number(tb.getAttribute("viewKey")); var current = ("currentObject" in client && client.currentObject == source); /* We want to play sounds if they're from a non-current view, or we don't * have focus at all. Also make sure stalk matches always play sounds. * Also make sure we don't play on the 2nd half of the flash (Callback). */ if (!callback && (!window.isFocused || !current || (what == "attention"))) { if (what == "attention") playEventSounds(source.TYPE, "stalk"); else if (what == "activity") playEventSounds(source.TYPE, "chat"); else if (what == "superfluous") playEventSounds(source.TYPE, "event"); } // Only change the tab's colour if it's not the active view. if (!current) { var state = tb.getAttribute("state"); if (state == what) { /* if the tab state has an equal priority to what we are setting * then blink it */ if (client.prefs["activityFlashDelay"] > 0) { tb.setAttribute("state", "normal"); setTimeout(setTabState, client.prefs["activityFlashDelay"], vk, what, true); } } else { if (state == "normal" || state == "superfluous" || (state == "activity" && what == "attention")) { /* if the tab state has a lower priority than what we are * setting, change it to the new state */ tb.setAttribute("state", what); /* we only change the activity list if priority has increased */ if (what == "attention") client.activityList[vk] = "!"; else if (what == "activity") client.activityList[vk] = "+"; else { /* this is functionally equivalent to "+" for now */ client.activityList[vk] = "-"; } updateTitle(); } else { /* the current state of the tab has a higher priority than the * new state. * blink the new lower state quickly, then back to the old */ if (client.prefs["activityFlashDelay"] > 0) { tb.setAttribute("state", what); setTimeout(setTabState, client.prefs["activityFlashDelay"], vk, state, true); } } } } } function notifyAttention (source) { if (typeof source != "object") source = client.viewsArray[source].source; if (client.currentObject != source) { var tb = dispatch("create-tab-for-view", { view: source }); var vk = Number(tb.getAttribute("viewKey")); tb.setAttribute ("state", "attention"); client.activityList[vk] = "!"; updateTitle(); } if (client.prefs["notify.aggressive"]) window.getAttention(); } function setDebugMode(mode) { if (mode.indexOf("t") != -1) client.debugHook.enabled = true; else client.debugHook.enabled = false; if (mode.indexOf("c") != -1) client.dbgContexts = true; else delete client.dbgContexts; if (mode.indexOf("d") != -1) client.dbgDispatch = true; else delete client.dbgDispatch; } function setListMode(mode) { var elem = document.getElementById("user-list"); if (mode) elem.setAttribute("mode", mode); else elem.removeAttribute("mode"); updateUserList(); } function updateUserList() { var node, chan; node = document.getElementById("user-list"); if (!node.view) return; // We'll lose the selection in a bit, if we don't save it if necessary: if (("currentObject" in client) && client.currentObject && client.currentObject.TYPE == "IRCChannel") { chan = client.currentObject; chan.userlistSelection = getSelectedNicknames(node, chan); } reSortUserlist(node); // If this is a channel, restore the selection in the userlist. if (chan) setSelectedNicknames(node, client.currentObject.userlistSelection); } function reSortUserlist(node) { const nsIXULSortService = Components.interfaces.nsIXULSortService; const isupports_uri = "@mozilla.org/xul/xul-sort-service;1"; var xulSortService = Components.classes[isupports_uri].getService(nsIXULSortService); if (!xulSortService) return; var sortResource; if (client.prefs["sortUsersByMode"]) sortResource = RES_PFX + "sortname"; else sortResource = RES_PFX + "unicodeName"; try { if ("sort" in xulSortService) xulSortService.sort(node, sortResource, "ascending"); else xulSortService.Sort(node, sortResource, "ascending"); } catch(ex) { dd("Exception calling xulSortService.sort()"); } } function getFrameForDOMWindow(window) { var frame; for (var i = 0; i < client.deck.childNodes.length; i++) { frame = client.deck.childNodes[i]; if (getContentWindow(frame) == window) return frame; } return undefined; } function replaceColorCodes(msg) { // mIRC codes: underline, bold, Original (reset), colors, reverse colors. msg = msg.replace(/(^|[^%])%U/g, "$1\x1f"); msg = msg.replace(/(^|[^%])%B/g, "$1\x02"); msg = msg.replace(/(^|[^%])%O/g, "$1\x0f"); msg = msg.replace(/(^|[^%])%C/g, "$1\x03"); msg = msg.replace(/(^|[^%])%R/g, "$1\x16"); // %%[UBOCR] --> %[UBOCR]. msg = msg.replace(/%(%[UBOCR])/g, "$1"); return msg; } function decodeColorCodes(msg) { // %[UBOCR] --> %%[UBOCR]. msg = msg.replace(/(%[UBOCR])/g, "%$1"); // Put %-codes back in place of special character codes. msg = msg.replace(/\x1f/g, "%U"); msg = msg.replace(/\x02/g, "%B"); msg = msg.replace(/\x0f/g, "%O"); msg = msg.replace(/\x03/g, "%C"); msg = msg.replace(/\x16/g, "%R"); return msg; } client.progressListener = new Object(); client.progressListener.QueryInterface = function qi(iid) { return this; } client.progressListener.onStateChange = function client_statechange (webProgress, request, stateFlags, status) { const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; const START = nsIWebProgressListener.STATE_START; const STOP = nsIWebProgressListener.STATE_STOP; const IS_NETWORK = nsIWebProgressListener.STATE_IS_NETWORK; const IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT; var frame; // We only care about the initial start of loading, not the loading of // and page sub-components (IS_DOCUMENT, etc.). if ((stateFlags & START) && (stateFlags & IS_NETWORK) && (stateFlags & IS_DOCUMENT)) { frame = getFrameForDOMWindow(webProgress.DOMWindow); if (!frame) { dd("can't find frame for window (start)"); try { webProgress.removeProgressListener(this); } catch(ex) { dd("Exception removing progress listener (start): " + ex); } } } // We only want to know when the *network* stops, not the page's // individual components (STATE_IS_REQUEST/STATE_IS_DOCUMENT/somesuch). else if ((stateFlags & STOP) && (stateFlags & IS_NETWORK)) { frame = getFrameForDOMWindow(webProgress.DOMWindow); if (!frame) { dd("can't find frame for window (stop)"); try { webProgress.removeProgressListener(this); } catch(ex) { dd("Exception removing progress listener (stop): " + ex); } } else { var cwin = getContentWindow(frame); if (cwin && "initOutputWindow" in cwin) { cwin.getMsg = getMsg; cwin.initOutputWindow(client, frame.source, onMessageViewClick); cwin.changeCSS(frame.source.getTimestampCSS("data"), "cz-timestamp-format"); cwin.changeCSS(frame.source.getFontCSS("data"), "cz-fonts"); scrollDown(frame, true); try { webProgress.removeProgressListener(this); } catch(ex) { dd("Exception removing progress listener (done): " + ex); } } } } } client.progressListener.onProgressChange = function client_progresschange (webProgress, request, currentSelf, totalSelf, currentMax, selfMax) { } client.progressListener.onLocationChange = function client_locationchange (webProgress, request, uri) { } client.progressListener.onStatusChange = function client_statuschange (webProgress, request, status, message) { } client.progressListener.onSecurityChange = function client_securitychange (webProgress, request, state) { } function syncOutputFrame(obj, nesting) { const nsIWebProgress = Components.interfaces.nsIWebProgress; const WINDOW = nsIWebProgress.NOTIFY_STATE_WINDOW; const NETWORK = nsIWebProgress.NOTIFY_STATE_NETWORK; const ALL = nsIWebProgress.NOTIFY_ALL; var iframe = obj.frame; function tryAgain(nLevel) { syncOutputFrame(obj, nLevel); }; if (typeof nesting != "number") nesting = 0; if (nesting > 10) { dd("Aborting syncOutputFrame, taken too many tries."); return; } try { if (("contentDocument" in iframe) && ("webProgress" in iframe)) { var url = obj.prefs["outputWindowURL"]; iframe.addProgressListener(client.progressListener, ALL); iframe.loadURI(url); } else { setTimeout(tryAgain, 500, nesting + 1); } } catch (ex) { dd("caught exception showing session view, will try again later."); dd(dumpObjectTree(ex) + "\n"); setTimeout(tryAgain, 500, nesting + 1); } } function createMessages(source) { playEventSounds(source.TYPE, "start"); source.messages = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:table"); source.messages.setAttribute ("class", "msg-table"); source.messages.setAttribute ("view-type", source.TYPE); var tbody = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:tbody"); source.messages.appendChild (tbody); source.messageCount = 0; } /* gets the toolbutton associated with an object * if |create| is present, and true, create if not found */ function getTabForObject (source, create) { var name; if (!ASSERT(source, "UNDEFINED passed to getTabForObject")) return null; if ("viewName" in source) { name = source.viewName; } else { ASSERT(0, "INVALID OBJECT passed to getTabForObject"); return null; } var tb, id = "tb[" + name + "]"; var matches = 1; for (var i in client.viewsArray) { if (client.viewsArray[i].source == source) { tb = client.viewsArray[i].tb; break; } else if (client.viewsArray[i].tb.getAttribute("id") == id) id = "tb[" + name + "<" + (++matches) + ">]"; } if (!tb && create) /* not found, create one */ { if (!("messages" in source) || source.messages == null) createMessages(source); var views = document.getElementById ("views-tbar-inner"); tb = document.createElement ("tab"); tb.setAttribute("ondraggesture", "nsDragAndDrop.startDrag(event, tabDNDObserver);"); tb.setAttribute("href", source.getURL()); tb.setAttribute("name", source.unicodeName); tb.setAttribute("onclick", "onTabClick(event, " + id.quote() + ");"); // This wouldn't be here if there was a supported CSS property for it. tb.setAttribute("crop", "center"); tb.setAttribute("context", "context:tab"); tb.setAttribute("tooltip", "xul-tooltip-node"); tb.setAttribute("class", "tab-bottom view-button"); tb.setAttribute("id", id); tb.setAttribute("state", "normal"); client.viewsArray.push ({source: source, tb: tb}); tb.setAttribute ("viewKey", client.viewsArray.length - 1); tb.view = source; if (matches > 1) tb.setAttribute("label", name + "<" + matches + ">"); else tb.setAttribute("label", name); views.appendChild(tb); var browser = document.createElement ("browser"); browser.setAttribute("class", "output-container"); // Only use type="chrome" if the host app supports it properly: if (client.hostCompat.typeChromeBrowser) browser.setAttribute("type", "chrome"); else browser.setAttribute("type", "content"); browser.setAttribute("flex", "1"); browser.setAttribute("tooltip", "html-tooltip-node"); browser.setAttribute("context", "context:messages"); //browser.setAttribute ("onload", "scrollDown(true);"); browser.setAttribute("onclick", "return onMessageViewClick(event)"); browser.setAttribute("ondragover", "nsDragAndDrop.dragOver(event, " + "contentDropObserver);"); browser.setAttribute("ondragdrop", "nsDragAndDrop.drop(event, contentDropObserver);"); browser.setAttribute("ondraggesture", "nsDragAndDrop.startDrag(event, " + "contentAreaDNDObserver);"); browser.source = source; source.frame = browser; ASSERT(client.deck, "no deck?"); client.deck.appendChild (browser); syncOutputFrame (source); } return tb; } var contentDropObserver = new Object(); contentDropObserver.onDragOver = function tabdnd_dover (aEvent, aFlavour, aDragSession) { if (aEvent.getPreventDefault()) return; if (aDragSession.sourceDocument == aEvent.view.document) { aDragSession.canDrop = false; return; } } contentDropObserver.onDrop = function tabdnd_drop (aEvent, aXferData, aDragSession) { var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType); if (!url || url.search(client.linkRE) == -1) return; if (url.search(/\.css$/i) != -1 && confirm (getMsg(MSG_TABDND_DROP, url))) dispatch("motif", {"motif": url}); else if (url.search(/^ircs?:\/\//i) != -1) dispatch("goto-url", {"url": url}); } contentDropObserver.getSupportedFlavours = function tabdnd_gsf () { var flavourSet = new FlavourSet(); flavourSet.appendFlavour("text/x-moz-url"); flavourSet.appendFlavour("application/x-moz-file", "nsIFile"); flavourSet.appendFlavour("text/unicode"); return flavourSet; } var tabDNDObserver = new Object(); tabDNDObserver.onDragStart = function tabdnd_dstart (aEvent, aXferData, aDragAction) { var tb = aEvent.currentTarget; var href = tb.getAttribute("href"); var name = tb.getAttribute("name"); aXferData.data = new TransferData(); /* x-moz-url has the format "\n", goodie */ aXferData.data.addDataForFlavour("text/x-moz-url", href + "\n" + name); aXferData.data.addDataForFlavour("text/unicode", href); aXferData.data.addDataForFlavour("text/html", "" + name + ""); } var userlistDNDObserver = new Object(); userlistDNDObserver.onDragStart = function userlistdnd_dstart(event, transferData, dragAction) { var col = new Object(), row = new Object(), cell = new Object(); var tree = document.getElementById('user-list'); tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, cell); // Check whether we're actually on a normal row and cell if (!cell.value || (row.value == -1)) return; var user = tree.contentView.getItemAtIndex(row.value).firstChild.firstChild; var nickname = user.getAttribute("unicodeName"); transferData.data = new TransferData(); transferData.data.addDataForFlavour("text/unicode", nickname); } function deleteTab (tb) { if (!ASSERT(tb.hasAttribute("viewKey"), "INVALID OBJECT passed to deleteTab (" + tb + ")")) { return null; } var i; var key = Number(tb.getAttribute("viewKey")); /* re-index higher toolbuttons */ for (i = key + 1; i < client.viewsArray.length; i++) { client.viewsArray[i].tb.setAttribute ("viewKey", i - 1); } arrayRemoveAt(client.viewsArray, key); var tbinner = document.getElementById("views-tbar-inner"); tbinner.removeChild(tb); return key; } function filterOutput(msg, msgtype, dest) { if ("outputFilters" in client) { for (var f in client.outputFilters) { if (client.outputFilters[f].enabled) msg = client.outputFilters[f].func(msg, msgtype, dest); } } return msg; } client.addNetwork = function cli_addnet(name, serverList, temporary) { client.networks[name] = new CIRCNetwork(name, serverList, client.eventPump, temporary); } client.removeNetwork = function cli_removenet(name) { // Allow network a chance to clean up any mess. if (typeof client.networks[name].destroy == "function") client.networks[name].destroy(); delete client.networks[name]; } client.connectToNetwork = function cli_connect(networkOrName, requireSecurity) { var network; var name; if (networkOrName instanceof CIRCNetwork) { network = networkOrName; } else { name = networkOrName; if (!(name in client.networks)) { display(getMsg(MSG_ERR_UNKNOWN_NETWORK, name), MT_ERROR); return null; } network = client.networks[name]; } name = network.unicodeName; if (!("messages" in network)) network.displayHere(getMsg(MSG_NETWORK_OPENED, name)); dispatch("set-current-view", { view: network }); if (network.isConnected()) { network.display(getMsg(MSG_ALREADY_CONNECTED, name)); return network; } if (network.state != NET_OFFLINE) return network; if (network.prefs["nickname"] == DEFAULT_NICK) network.prefs["nickname"] = prompt(MSG_ENTER_NICK, DEFAULT_NICK); if (!("connecting" in network)) network.display(getMsg(MSG_NETWORK_CONNECTING, name)); network.connect(requireSecurity); network.updateHeader(); client.updateHeader(); updateTitle(); return network; } client.getURL = function cli_geturl () { return "irc://"; } client.load = function cli_load(url, scope) { if (!("_loader" in client)) { const LOADER_CTRID = "@mozilla.org/moz/jssubscript-loader;1"; const mozIJSSubScriptLoader = Components.interfaces.mozIJSSubScriptLoader; var cls; if ((cls = Components.classes[LOADER_CTRID])) client._loader = cls.getService(mozIJSSubScriptLoader); } return client._loader.loadSubScript(url, scope); } client.sayToCurrentTarget = function cli_say(msg) { if ("say" in client.currentObject) { msg = filterOutput(msg, "PRIVMSG", client.currentObject); display(msg, "PRIVMSG", "ME!", client.currentObject); client.currentObject.say(msg); return; } switch (client.currentObject.TYPE) { case "IRCClient": dispatch("eval", {expression: msg}); break; default: if (msg != "") display(MSG_ERR_NO_DEFAULT, MT_ERROR); break; } } CIRCNetwork.prototype.__defineGetter__("prefs", net_getprefs); function net_getprefs() { if (!("_prefs" in this)) { this._prefManager = getNetworkPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefs; } CIRCNetwork.prototype.__defineGetter__("prefManager", net_getprefmgr); function net_getprefmgr() { if (!("_prefManager" in this)) { this._prefManager = getNetworkPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefManager; } CIRCServer.prototype.__defineGetter__("prefs", srv_getprefs); function srv_getprefs() { return this.parent.prefs; } CIRCServer.prototype.__defineGetter__("prefManager", srv_getprefmgr); function srv_getprefmgr() { return this.parent.prefManager; } CIRCChannel.prototype.__defineGetter__("prefs", chan_getprefs); function chan_getprefs() { if (!("_prefs" in this)) { this._prefManager = getChannelPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefs; } CIRCChannel.prototype.__defineGetter__("prefManager", chan_getprefmgr); function chan_getprefmgr() { if (!("_prefManager" in this)) { this._prefManager = getChannelPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefManager; } CIRCUser.prototype.__defineGetter__("prefs", usr_getprefs); function usr_getprefs() { if (!("_prefs" in this)) { this._prefManager = getUserPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefs; } CIRCUser.prototype.__defineGetter__("prefManager", usr_getprefmgr); function usr_getprefmgr() { if (!("_prefManager" in this)) { this._prefManager = getUserPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefManager; } CIRCDCCUser.prototype.__defineGetter__("prefs", dccusr_getprefs); function dccusr_getprefs() { if (!("_prefs" in this)) { this._prefManager = getDCCUserPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefs; } CIRCDCCUser.prototype.__defineGetter__("prefManager", dccusr_getprefmgr); function dccusr_getprefmgr() { if (!("_prefManager" in this)) { this._prefManager = getDCCUserPrefManager(this); this._prefs = this._prefManager.prefs; } return this._prefManager; } CIRCDCCChat.prototype.__defineGetter__("prefs", dccchat_getprefs); function dccchat_getprefs() { return this.user.prefs; } CIRCDCCChat.prototype.__defineGetter__("prefManager", dccchat_getprefmgr); function dccchat_getprefmgr() { return this.user.prefManager; } CIRCDCCFileTransfer.prototype.__defineGetter__("prefs", dccfile_getprefs); function dccfile_getprefs() { return this.user.prefs; } CIRCDCCFileTransfer.prototype.__defineGetter__("prefManager", dccfile_getprefmgr); function dccfile_getprefmgr() { return this.user.prefManager; } CIRCNetwork.prototype.display = function net_display (message, msgtype, sourceObj, destObj) { var o = getObjectDetails(client.currentObject); if (client.SLOPPY_NETWORKS && client.currentObject != this && o.network == this && o.server && o.server.isConnected) { client.currentObject.display (message, msgtype, sourceObj, destObj); } else { this.displayHere (message, msgtype, sourceObj, destObj); } } CIRCUser.prototype.display = function usr_display(message, msgtype, sourceObj, destObj) { if ("messages" in this) { this.displayHere (message, msgtype, sourceObj, destObj); } else { var o = getObjectDetails(client.currentObject); if (o.server && o.server.isConnected && o.network == this.parent.parent && client.currentObject.TYPE != "IRCUser") client.currentObject.display (message, msgtype, sourceObj, destObj); else this.parent.parent.displayHere (message, msgtype, sourceObj, destObj); } } CIRCDCCChat.prototype.display = CIRCDCCFileTransfer.prototype.display = function dcc_display(message, msgtype, sourceObj, destObj) { var o = getObjectDetails(client.currentObject); if ("messages" in this) this.displayHere(message, msgtype, sourceObj, destObj); else client.currentObject.display(message, msgtype, sourceObj, destObj); } function feedback(e, message, msgtype, sourceObj, destObj) { if ("isInteractive" in e && e.isInteractive) display(message, msgtype, sourceObj, destObj); } CIRCChannel.prototype.feedback = CIRCNetwork.prototype.feedback = CIRCUser.prototype.feedback = CIRCDCCChat.prototype.feedback = CIRCDCCFileTransfer.prototype.feedback = client.feedback = function this_feedback(e, message, msgtype, sourceObj, destObj) { if ("isInteractive" in e && e.isInteractive) this.displayHere(message, msgtype, sourceObj, destObj); } function display (message, msgtype, sourceObj, destObj) { client.currentObject.display (message, msgtype, sourceObj, destObj); } client.getTimestampCSS = CIRCNetwork.prototype.getTimestampCSS = CIRCChannel.prototype.getTimestampCSS = CIRCUser.prototype.getTimestampCSS = CIRCDCCChat.prototype.getTimestampCSS = CIRCDCCFileTransfer.prototype.getTimestampCSS = function this_getTimestampCSS(format) { /* Wow, this is cool. We just put together a CSS-rule string based on the * "timestampFormat" preferences. *This* is what CSS is all about. :) * We also provide a "data: URL" format, to simplify other code. */ var css; if (this.prefs["timestamps"]) { /* Hack. To get around a Mozilla bug, we must force the display back * to a displayed value. */ css = ".msg-timestamp { display: table-cell; } " + ".msg-timestamp:before { content: '" + this.prefs["timestampFormat"] + "'; }"; var letters = new Array('y', 'm', 'd', 'h', 'n', 's'); for (var i = 0; i < letters.length; i++) { css = css.replace("%" + letters[i], "' attr(time-" + letters[i] + ") '"); } } else { /* Completely remove the s if they're off, neatens display. */ css = ".msg-timestamp { display: none; }"; } if (format == "data") return "data:text/css," + encodeURIComponent(css); return css; } client.getFontCSS = CIRCNetwork.prototype.getFontCSS = CIRCChannel.prototype.getFontCSS = CIRCUser.prototype.getFontCSS = CIRCDCCChat.prototype.getFontCSS = CIRCDCCFileTransfer.prototype.getFontCSS = function this_getFontCSS(format) { /* See this_getTimestampCSS. */ var css; var fs; var fn; if (this.prefs["font.family"] != "default") fn = "font-family: " + this.prefs["font.family"] + ";"; else fn = "font-family: inherit;"; if (this.prefs["font.size"] != 0) fs = "font-size: " + this.prefs["font.size"] + "pt;"; else fs = "font-size: medium;"; css = "body.chatzilla-body { " + fs + fn + " }"; if (format == "data") return "data:text/css," + encodeURIComponent(css); return css; } client.display = client.displayHere = CIRCNetwork.prototype.displayHere = CIRCChannel.prototype.display = CIRCChannel.prototype.displayHere = CIRCUser.prototype.displayHere = CIRCDCCChat.prototype.displayHere = CIRCDCCFileTransfer.prototype.displayHere = function __display(message, msgtype, sourceObj, destObj) { // We like some control on the number of digits. function formatTimeNumber (num, digits) { var rv = num.toString(); while (rv.length < digits) rv = "0" + rv; return rv; }; // We need a message type, assume "INFO". if (!msgtype) msgtype = MT_INFO; var blockLevel = false; /* true if this row should be rendered at block * level, (like, if it has a really long nickname * that might disturb the rest of the layout) */ var o = getObjectDetails(this); /* get the skinny on |this| */ // Get the 'me' object, so we can be sure to get the attributes right. var me; if ("me" in this) me = this.me; else if (o.server && "me" in o.server) me = o.server.me; // Let callers get away with "ME!" and we have to substitute here. if (sourceObj == "ME!") sourceObj = me; if (destObj == "ME!") destObj = me; // Get the TYPE of the source object. var fromType = (sourceObj && sourceObj.TYPE) ? sourceObj.TYPE : "unk"; // Is the source a user? var fromUser = (fromType.search(/IRC.*User/) != -1); // Get some sort of "name" for the source. var fromAttr = ""; if (sourceObj) { if ("unicodeName" in sourceObj) fromAttr = sourceObj.unicodeName; else if ("name" in sourceObj) fromAttr = sourceObj.name; else fromAttr = sourceObj.viewName; } // Attach "ME!" if appropriate, so motifs can style differently. if (sourceObj == me) fromAttr = fromAttr + " ME!"; // Get the dest TYPE too... var toType = (destObj) ? destObj.TYPE : "unk"; // Is the dest a user? var toUser = (toType.search(/IRC.*User/) != -1); // Get a dest name too... var toAttr = ""; if (destObj) { if ("unicodeName" in destObj) toAttr = destObj.unicodeName; else if ("name" in destObj) toAttr = destObj.name; else toAttr = destObj.viewName; } // Also do "ME!" work for the dest. if (destObj && destObj == me) toAttr = me.unicodeName + " ME!"; /* isImportant means to style the messages as important, and flash the * window, getAttention means just flash the window. */ var isImportant = false, getAttention = false, isSuperfluous = false; var viewType = this.TYPE; var code; var d = new Date(); var dateInfo = { y: formatTimeNumber(d.getFullYear(), 4), m: formatTimeNumber(d.getMonth() + 1, 2), d: formatTimeNumber(d.getDate(), 2), h: formatTimeNumber(d.getHours(), 2), n: formatTimeNumber(d.getMinutes(), 2), s: formatTimeNumber(d.getSeconds(), 2) }; // Statusbar text, and the line that gets saved to the log. var statusString; var logString; var dtf = client.dtFormatter; var timeStamp = dtf.FormatDateTime("", dtf.dateFormatShort, dtf.timeFormatNoSeconds, d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds() ); logString = "[" + timeStamp + "] "; if (fromUser) { statusString = getMsg(MSG_FMT_STATUS, [timeStamp, sourceObj.unicodeName + "!" + sourceObj.name + "@" + sourceObj.host]); } else { var name; if (sourceObj) name = sourceObj.viewName; else name = this.viewName; statusString = getMsg(MSG_FMT_STATUS, [timeStamp, name]); } // The table row, and it's attributes. var msgRow = document.createElementNS("http://www.w3.org/1999/xhtml", "html:tr"); msgRow.setAttribute("class", "msg"); msgRow.setAttribute("msg-type", msgtype); msgRow.setAttribute("msg-dest", toAttr); msgRow.setAttribute("dest-type", toType); msgRow.setAttribute("view-type", viewType); msgRow.setAttribute("statusText", statusString); if (fromAttr) { if (fromUser) msgRow.setAttribute("msg-user", fromAttr); else msgRow.setAttribute("msg-source", fromAttr); } if (isImportant) msgTimestamp.setAttribute ("important", "true"); // Timestamp cell. var msgRowTimestamp = document.createElementNS("http://www.w3.org/1999/xhtml", "html:td"); msgRowTimestamp.setAttribute("class", "msg-timestamp"); for (var key in dateInfo) msgRowTimestamp.setAttribute("time-" + key, dateInfo[key]); var canMergeData; var msgRowSource, msgRowType, msgRowData; if (fromUser && msgtype.match(/^(PRIVMSG|ACTION|NOTICE)$/)) { var nick = sourceObj.unicodeName; var decorSt = ""; var decorEn = ""; // Set default decorations. if (msgtype == "ACTION") { decorSt = "* "; } else { decorSt = "<"; decorEn = ">"; } var nickURL; if ((sourceObj != me) && ("getURL" in sourceObj)) nickURL = sourceObj.getURL(); if (sourceObj != me) { // Not from us... if (destObj == me) { // ...but to us. Messages from someone else to us. getAttention = true; this.defaultCompletion = "/msg " + nick + " "; // If this is a private message, and it's not in a query view, // use *nick* instead of . if ((msgtype != "ACTION") && (this.TYPE != "IRCUser")) { decorSt = "*"; decorEn = "*"; } } else { // ...or to us. Messages from someone else to channel or similar. if ((typeof message == "string") && me) { isImportant = msgIsImportant(message, nick, o.network); if (isImportant) { this.defaultCompletion = nick + client.prefs["nickCompleteStr"] + " "; } } } } else { // Messages from us, on a channel or network view, to a user if (toUser && (this.TYPE != "IRCUser")) { nick = destObj.unicodeName; decorSt = ">"; decorEn = "<"; } } // Log the nickname in the same format as we'll let the user copy. logString += decorSt + nick + decorEn + " "; // Mark makes alternate "talkers" show up in different shades. //if (!("mark" in this)) // this.mark = "odd"; if (!("lastNickDisplayed" in this) || this.lastNickDisplayed != nick) { this.lastNickDisplayed = nick; this.mark = (("mark" in this) && this.mark == "even") ? "odd" : "even"; } msgRowSource = document.createElementNS("http://www.w3.org/1999/xhtml", "html:td"); msgRowSource.setAttribute("class", "msg-user"); // Make excessive nicks get shunted. if (nick && (nick.length > client.MAX_NICK_DISPLAY)) blockLevel = true; if (decorSt) msgRowSource.appendChild(newInlineText(decorSt, "chatzilla-decor")); if (nickURL) { var nick_anchor = document.createElementNS("http://www.w3.org/1999/xhtml", "html:a"); nick_anchor.setAttribute("class", "chatzilla-link"); nick_anchor.setAttribute("href", nickURL); nick_anchor.appendChild(newInlineText(nick)); msgRowSource.appendChild(nick_anchor); } else { msgRowSource.appendChild(newInlineText(nick)); } if (decorEn) msgRowSource.appendChild(newInlineText(decorEn, "chatzilla-decor")); canMergeData = this.prefs["collapseMsgs"]; } else { isSuperfluous = true; if (!client.debugHook.enabled && msgtype in client.responseCodeMap) { code = client.responseCodeMap[msgtype]; } else { if (!client.debugHook.enabled && client.HIDE_CODES) code = client.DEFAULT_RESPONSE_CODE; else code = "[" + msgtype + "]"; } /* Display the message code */ msgRowType = document.createElementNS("http://www.w3.org/1999/xhtml", "html:td"); msgRowType.setAttribute("class", "msg-type"); msgRowType.appendChild(newInlineText(code)); logString += code + " "; } if (message) { msgRowData = document.createElementNS("http://www.w3.org/1999/xhtml", "html:td"); msgRowData.setAttribute("class", "msg-data"); if (typeof message == "string") { msgRowData.appendChild(stringToMsg(message, this)); logString += message; } else { msgRowData.appendChild(message); logString += message.innerHTML.replace(/<[^<]*>/g, ""); } } if ("mark" in this) msgRow.setAttribute("mark", this.mark); if (isImportant) msgRow.setAttribute ("important", "true"); // Timestamps first... msgRow.appendChild(msgRowTimestamp); // Now do the rest of the row, after block-level stuff. if (msgRowSource) msgRow.appendChild(msgRowSource); else msgRow.appendChild(msgRowType); if (msgRowData) msgRow.appendChild(msgRowData); if (blockLevel) { /* putting a div here crashes mozilla, so fake it with nested tables * for now */ var tr = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:tr"); tr.setAttribute ("class", "msg-nested-tr"); var td = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:td"); td.setAttribute ("class", "msg-nested-td"); td.setAttribute ("colspan", "3"); tr.appendChild(td); var table = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:table"); table.setAttribute ("class", "msg-nested-table"); td.appendChild (table); var tbody = document.createElementNS ("http://www.w3.org/1999/xhtml", "html:tbody"); tbody.appendChild(msgRow); table.appendChild(tbody); msgRow = tr; } // Actually add the item. addHistory (this, msgRow, canMergeData); // Update attention states... if (isImportant || getAttention) { setTabState(this, "attention"); if (client.prefs["notify.aggressive"]) window.getAttention(); } else { if (isSuperfluous) { setTabState(this, "superfluous"); } else { setTabState(this, "activity"); } } // Copy Important Messages [to network view]. if (isImportant && client.prefs["copyMessages"] && (o.network != this)) { o.network.displayHere("{" + this.unicodeName + "} " + message, msgtype, sourceObj, destObj); } // Log file time! if (this.prefs["log"]) { if (!this.logFile) client.openLogFile(this); try { this.logFile.write(fromUnicode(logString + client.lineEnd, "utf-8")); } catch (ex) { // Stop logging before showing any messages! this.prefs["log"] = false; dd("Log file write error: " + formatException(ex)); this.displayHere(getMsg(MSG_LOGFILE_WRITE_ERROR, getLogPath(this)), "ERROR"); } } } function addHistory (source, obj, mergeData) { if (!("messages" in source) || (source.messages == null)) createMessages(source); var tbody = source.messages.firstChild; var appendTo = tbody; var needScroll = false; if (mergeData) { var inobj = obj; // This gives us the non-nested row when there is nesting. if (inobj.className == "msg-nested-tr") inobj = inobj.firstChild.firstChild.firstChild.firstChild; var thisUserCol = inobj.firstChild; while (thisUserCol && !thisUserCol.className.match(/^(msg-user|msg-type)$/)) thisUserCol = thisUserCol.nextSibling; var thisMessageCol = inobj.firstChild; while (thisMessageCol && !(thisMessageCol.className == "msg-data")) thisMessageCol = thisMessageCol.nextSibling; var ci = findPreviousColumnInfo(source.messages); var nickColumns = ci.nickColumns; var rowExtents = ci.extents; var nickColumnCount = nickColumns.length; var lastRowSpan, sameNick, sameDest, haveSameType, needSameType; var isAction, collapseActions; if (nickColumnCount == 0) // No message to collapse to. { sameNick = sameDest = needSameType = haveSameType = false; lastRowSpan = 0; } else // 1 or more messages, check for doubles { var lastRow = nickColumns[nickColumnCount - 1].parentNode; // What was the span last time? lastRowSpan = Number(nickColumns[0].getAttribute("rowspan")); // Are we the same user as last time? sameNick = (lastRow.getAttribute("msg-user") == inobj.getAttribute("msg-user")); // Do we have the same destination as last time? sameDest = (lastRow.getAttribute("msg-dest") == inobj.getAttribute("msg-dest")); // Is this message the same type as the last one? haveSameType = (lastRow.getAttribute("msg-type") == inobj.getAttribute("msg-type")); // Is either of the messages an action? We may not want to collapse // depending on the collapseActions pref isAction = ((inobj.getAttribute("msg-type") == "ACTION") || (lastRow.getAttribute("msg-type") == "ACTION")); // Do we collapse actions? collapseActions = source.prefs["collapseActions"]; // Does the motif collapse everything, regardless of type? // NOTE: the collapseActions pref can override this for actions needSameType = !(("motifSettings" in source) && source.motifSettings && ("collapsemore" in source.motifSettings)); } if (sameNick && sameDest && (haveSameType || !needSameType) && (!isAction || collapseActions)) { obj = inobj; if (ci.nested) appendTo = source.messages.firstChild.lastChild.firstChild.firstChild.firstChild; if (obj.getAttribute("important")) { nickColumns[nickColumnCount - 1].setAttribute("important", true); } // Remove nickname column from new row. obj.removeChild(thisUserCol); // Expand previous grouping's nickname cell(s) to fill-in the gap. for (var i = 0; i < nickColumns.length; ++i) nickColumns[i].setAttribute("rowspan", rowExtents.length + 1); } } if ("frame" in source) needScroll = checkScroll(source.frame); if (obj) appendTo.appendChild(obj); if (source.MAX_MESSAGES) { if (typeof source.messageCount != "number") source.messageCount = 1; else source.messageCount++; if (source.messageCount > source.MAX_MESSAGES) { if (client.PRINT_DIRECTION == 1) { var height = tbody.firstChild.scrollHeight; var window = getContentWindow(source.frame); var x = window.pageXOffset; var y = window.pageYOffset; tbody.removeChild (tbody.firstChild); --source.messageCount; while (tbody.firstChild && tbody.firstChild.childNodes[1] && tbody.firstChild.childNodes[1].getAttribute("class") == "msg-data") { --source.messageCount; tbody.removeChild (tbody.firstChild); } if (!checkScroll(source.frame) && (y > height)) window.scrollTo(x, y - height); } else { tbody.removeChild (tbody.lastChild); --source.messageCount; while (tbody.lastChild && tbody.lastChild.childNodes[1] && tbody.lastChild.childNodes[1].getAttribute("class") == "msg-data") { --source.messageCount; tbody.removeChild (tbody.lastChild); } } } } if (needScroll) { scrollDown(source.frame, true); setTimeout(scrollDown, 500, source.frame, false); setTimeout(scrollDown, 1000, source.frame, false); setTimeout(scrollDown, 2000, source.frame, false); } } function findPreviousColumnInfo(table) { // All the rows in the grouping (for merged rows). var extents = new Array(); // Get the last row in the table. var tr = table.firstChild.lastChild; // Bail if there's no rows. if (!tr) return {extents: [], nickColumns: [], nested: false}; // Get message type. if (tr.className == "msg-nested-tr") { var rv = findPreviousColumnInfo(tr.firstChild.firstChild); rv.nested = true; return rv; } // Now get the read one... var className = (tr && tr.childNodes[1]) ? tr.childNodes[1].getAttribute("class") : ""; // Keep going up rows until you find the first in a group. // This will go up until it hits the top of a multiline/merged block. while (tr && tr.childNodes[1] && className.search(/msg-user|msg-type/) == -1) { extents.push(tr); tr = tr.previousSibling; if (tr && tr.childNodes[1]) className = tr.childNodes[1].getAttribute("class"); } // If we ran out of rows, or it's not a talking line, we're outta here. if (!tr || className != "msg-user") return {extents: [], nickColumns: [], nested: false}; extents.push(tr); // Time to collect the nick data... var nickCol = tr.firstChild; // All the cells that contain nickname info. var nickCols = new Array(); while (nickCol) { // Just collect nickname column cells. if (nickCol.getAttribute("class") == "msg-user") nickCols.push(nickCol); nickCol = nickCol.nextSibling; } // And we're done. return {extents: extents, nickColumns: nickCols, nested: false}; } function getLogPath(obj) { // If we're logging, return the currently-used URL. if (obj.logFile) return getURLSpecFromFile(obj.logFile.path); // If not, return the ideal URL. return getURLSpecFromFile(obj.prefs["logFileName"]); } client.getConnectionCount = function cli_gccount () { var count = 0; for (var n in client.networks) { if (client.networks[n].isConnected()) ++count; } return count; } client.quit = function cli_quit (reason) { var net, netReason; for (var n in client.networks) { net = client.networks[n]; if (net.isConnected()) { netReason = (reason ? reason : net.prefs["defaultQuitMsg"]); netReason = (netReason ? netReason : client.userAgent); net.quit(netReason); } } } client.wantToQuit = function cli_wantToQuit(reason, deliberate) { var close = true; if (client.prefs["warnOnClose"] && !deliberate) { const buttons = ["!yes", "!no"]; var checkState = { value: true }; var rv = confirmEx(MSG_CONFIRM_QUIT, buttons, 0, MSG_WARN_ON_EXIT, checkState); close = (rv == 0); client.prefs["warnOnClose"] = checkState.value; } if (close) { client.userClose = true; display(MSG_CLOSING); client.quit(reason); } } /* gets a tab-complete match for the line of text specified by |line|. * wordStart is the position within |line| that starts the word being matched, * wordEnd marks the end position. |cursorPos| marks the position of the caret * in the textbox. */ client.performTabMatch = function gettabmatch_usr (line, wordStart, wordEnd, word, cursorPos) { if (wordStart != 0 || line[0] != client.COMMAND_CHAR) return null; var matches = client.commandManager.listNames(word.substr(1), CMD_CONSOLE); if (matches.length == 1 && wordEnd == line.length) { matches[0] = client.COMMAND_CHAR + matches[0] + " "; } else { for (var i in matches) matches[i] = client.COMMAND_CHAR + matches[i]; } return matches; } client.openLogFile = function cli_startlog (view) { function getNextLogFileDate() { var d = new Date(); d.setMilliseconds(0); d.setSeconds(0); d.setMinutes(0); switch (view.smallestLogInterval) { case "h": return d.setHours(d.getHours() + 1); case "d": d.setHours(0); return d.setDate(d.getDate() + 1); case "m": d.setHours(0); d.setDate(1); return d.setMonth(d.getMonth() + 1); case "y": d.setHours(0); d.setDate(1); d.setMonth(0); return d.setFullYear(d.getFullYear() + 1); } //XXXhack: This should work... return Infinity; }; const NORMAL_FILE_TYPE = Components.interfaces.nsIFile.NORMAL_FILE_TYPE; try { var file = new LocalFile(view.prefs["logFileName"]); if (!file.localFile.exists()) { // futils.umask may be 0022. Result is 0644. file.localFile.create(NORMAL_FILE_TYPE, 0666 & ~futils.umask); } view.logFile = fopen(file.localFile, ">>"); // If we're here, it's safe to say when we should re-open: view.nextLogFileDate = getNextLogFileDate(); } catch (ex) { view.prefs["log"] = false; dd("Log file open error: " + formatException(ex)); view.displayHere(getMsg(MSG_LOGFILE_ERROR, getLogPath(view)), MT_ERROR); return; } if (!("logFileWrapping" in view) || !view.logFileWrapping) view.displayHere(getMsg(MSG_LOGFILE_OPENED, getLogPath(view))); view.logFileWrapping = false; } client.closeLogFile = function cli_stoplog(view, wrapping) { if ("frame" in view && !wrapping) view.displayHere(getMsg(MSG_LOGFILE_CLOSING, getLogPath(view))); view.logFileWrapping = Boolean(wrapping); if (view.logFile) { view.logFile.close(); view.logFile = null; } } function checkLogFiles() { // For every view that has a logfile, check if we need a different file // based on the current date and the logfile preference. We close the // current logfile, and display will open the new one based on the pref // when it's needed. var d = new Date(); for (var n in client.networks) { var net = client.networks[n]; if (net.logFile && (d > net.nextLogFileDate)) client.closeLogFile(net, true); if (("primServ" in net) && net.primServ && ("channels" in net.primServ)) { for (var c in net.primServ.channels) { var chan = net.primServ.channels[c]; if (chan.logFile && (d > chan.nextLogFileDate)) client.closeLogFile(chan, true); } } if ("users" in net) { for (var u in net.users) { var user = net.users[u]; if (user.logFile && (d > user.nextLogFileDate)) client.closeLogFile(user, true); } } } for (var dc in client.dcc.chats) { var dccChat = client.dcc.chats[dc]; if (dccChat.logFile && (d > dccChat.nextLogFileDate)) client.closeLogFile(dccChat, true); } for (var df in client.dcc.files) { var dccFile = client.dcc.files[df]; if (dccFile.logFile && (d > dccFile.nextLogFileDate)) client.closeLogFile(dccFile, true); } // Don't forget about the client tab: if (client.logFile && (d > client.nextLogFileDate)) client.closeLogFile(client, true); // We use the same line again to make sure we keep a constant offset // from the full hour, in case the timers go crazy at some point. setTimeout("checkLogFiles()", 3602000 - (Number(new Date()) % 3600000)); } CIRCChannel.prototype.getLCFunction = CIRCNetwork.prototype.getLCFunction = CIRCUser.prototype.getLCFunction = CIRCDCCChat.prototype.getLCFunction = CIRCDCCFileTransfer.prototype.getLCFunction = function getlcfn() { var details = getObjectDetails(this); var lcFn; if (details.server) { lcFn = function(text) { return details.server.toLowerCase(text); } } return lcFn; } CIRCChannel.prototype.performTabMatch = CIRCNetwork.prototype.performTabMatch = CIRCUser.prototype.performTabMatch = CIRCDCCChat.prototype.performTabMatch = CIRCDCCFileTransfer.prototype.performTabMatch = function gettabmatch_other (line, wordStart, wordEnd, word, cursorpos, lcFn) { if (wordStart == 0 && line[0] == client.COMMAND_CHAR) { return client.performTabMatch(line, wordStart, wordEnd, word, cursorpos); } var matchList = new Array(); var users; var channels; var userIndex = -1; var details = getObjectDetails(this); if (details.channel && word == details.channel.unicodeName[0]) { /* When we have #, we just want the current channel, if possible. */ matchList.push(details.channel.unicodeName); } else { /* Ok, not # or no current channel, so get the full list. */ if (details.channel) users = details.channel.users; if (details.server) { channels = details.server.channels; for (var c in channels) matchList.push(channels[c].unicodeName); if (!users) users = details.server.users; } if (users) { userIndex = matchList.length; for (var n in users) matchList.push(users[n].unicodeName); } } var matches = matchEntry(word, matchList, lcFn); var list = new Array(); for (var i = 0; i < matches.length; i++) list.push(matchList[matches[i]]); if (list.length == 1) { if (users && (userIndex >= 0) && (matches[0] >= userIndex)) { if (wordStart == 0) list[0] += client.prefs["nickCompleteStr"]; } if (wordEnd == line.length) { /* add a space if the word is at the end of the line. */ list[0] += " "; } } return list; } CIRCChannel.prototype.getGraphResource = function my_graphres () { if (!("rdfRes" in this)) { var id = RES_PFX + "CHANNEL:" + this.parent.parent.unicodeName + ":" + escape(this.unicodeName); this.rdfRes = client.rdf.GetResource(id); } return this.rdfRes; } CIRCUser.prototype.getGraphResource = function usr_graphres() { if (!ASSERT(this.TYPE == "IRCChanUser", "cuser.getGraphResource called on wrong object")) { return null; } var rdf = client.rdf; if (!("rdfRes" in this)) { if (!("nextResID" in CIRCUser)) CIRCUser.nextResID = 0; this.rdfRes = rdf.GetResource(RES_PFX + "CUSER:" + this.parent.parent.parent.unicodeName + ":" + this.parent.unicodeName + ":" + CIRCUser.nextResID++); //dd ("created cuser resource " + this.rdfRes.Value); rdf.Assert (this.rdfRes, rdf.resNick, rdf.GetLiteral(this.unicodeName)); rdf.Assert (this.rdfRes, rdf.resUniName, rdf.GetLiteral(this.unicodeName)); rdf.Assert (this.rdfRes, rdf.resUser, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resHost, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resSortName, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resFounder, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resAdmin, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resOp, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resHalfOp, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resVoice, rdf.litUnk); rdf.Assert (this.rdfRes, rdf.resAway, rdf.litUnk); this.updateGraphResource(); } return this.rdfRes; } CIRCUser.prototype.updateGraphResource = function usr_updres() { if (!ASSERT(this.TYPE == "IRCChanUser", "cuser.updateGraphResource called on wrong object")) { return; } if (!("rdfRes" in this)) { this.getGraphResource(); return; } var rdf = client.rdf; rdf.Change (this.rdfRes, rdf.resUniName, rdf.GetLiteral(this.unicodeName)); if (this.name) rdf.Change (this.rdfRes, rdf.resUser, rdf.GetLiteral(this.name)); else rdf.Change (this.rdfRes, rdf.resUser, rdf.litUnk); if (this.host) rdf.Change (this.rdfRes, rdf.resHost, rdf.GetLiteral(this.host)); else rdf.Change (this.rdfRes, rdf.resHost, rdf.litUnk); // Check for the highest mode the user has. const userModes = this.parent.parent.userModes; var modeLevel = 0; var mode; for (var i = 0; i < this.modes.length; i++) { for (var j = 0; j < userModes.length; j++) { if (userModes[j].mode == this.modes[i]) { if (userModes.length - j > modeLevel) { modeLevel = userModes.length - j; mode = userModes[j]; } break; } } } // Counts numerically down from 9. var sortname = (9 - modeLevel) + "-" + this.unicodeName; // We want to show mode symbols, but only for modes we don't 'style'. var displayname = this.unicodeName; if (mode && !mode.mode.match(/^[qaohv]$/)) displayname = mode.symbol + " " + displayname; rdf.Change(this.rdfRes, rdf.resNick, rdf.GetLiteral(displayname)); rdf.Change(this.rdfRes, rdf.resSortName, rdf.GetLiteral(sortname)); rdf.Change(this.rdfRes, rdf.resFounder, this.isFounder ? rdf.litTrue : rdf.litFalse); rdf.Change(this.rdfRes, rdf.resAdmin, this.isAdmin ? rdf.litTrue : rdf.litFalse); rdf.Change(this.rdfRes, rdf.resOp, this.isOp ? rdf.litTrue : rdf.litFalse); rdf.Change(this.rdfRes, rdf.resHalfOp, this.isHalfOp ? rdf.litTrue : rdf.litFalse); rdf.Change(this.rdfRes, rdf.resVoice, this.isVoice ? rdf.litTrue : rdf.litFalse); rdf.Change(this.rdfRes, rdf.resAway, this.isAway ? rdf.litTrue : rdf.litFalse); }