diff --git a/TODO b/TODO index 598edda..90c9beb 100644 --- a/TODO +++ b/TODO @@ -6,3 +6,6 @@ General: UI: - replace qwebirc UI elements (logo, pages, etc) + +README: +- Document dependencies (swftools added by flashsocket) diff --git a/bin/compile.py b/bin/compile.py index 14a72c4..d68c610 100644 --- a/bin/compile.py +++ b/bin/compile.py @@ -75,21 +75,21 @@ def merge_files(output, files, root_path=lambda x: x): f2.close() f.close() +def mkdir(path): + try: + os.mkdir(path) + except: + pass + def main(outputdir=".", produce_debug=True): ID = pagegen.getgitid() pagegen.main(outputdir, produce_debug=produce_debug) coutputdir = os.path.join(outputdir, "compiled") - try: - os.mkdir(coutputdir) - except: - pass - - try: - os.mkdir(os.path.join(outputdir, "static", "css")) - except: - pass + mkdir(coutputdir) + mkdir(os.path.join(outputdir, "static", "css")) + mkdir(os.path.join(outputdir, "static", "swf")) for uiname, value in pages.UIs.items(): csssrc = pagegen.csslist(uiname, True) @@ -112,6 +112,8 @@ def main(outputdir=".", produce_debug=True): alljs.append(os.path.join("js", "ui", "frontends", y + ".js")) jmerge_files(outputdir, "js", uiname + "-" + ID, alljs, file_prefix="QWEBIRC_BUILD=\"" + ID + "\";\n") + subprocess.call(["as3compile", os.path.join(outputdir, "swf", "flashsocket.as"), "-o", os.path.join(outputdir, "static", "swf", "flashsocket.swf")]) + os.rmdir(coutputdir) f = open(".compiled", "w") diff --git a/bin/pagegen.py b/bin/pagegen.py index d963698..869efaf 100755 --- a/bin/pagegen.py +++ b/bin/pagegen.py @@ -61,7 +61,18 @@ def producehtml(name, debug): div = ui.get("div", "") customcss = "\n".join(" " % (config.frontend["static_base_url"], x) for x in ui.get("customcss", [])) customjs = "\n".join(" " % (config.frontend["static_base_url"], x) for x in ui.get("customjs", [])) - + flash = """ +
+
+ +
""" % (config.frontend["static_base_url"]) return """%s @@ -84,9 +95,10 @@ def producehtml(name, debug):
Javascript is required to use IRC.
%s +%s -""" % (ui["doctype"], config.frontend["app_title"], config.frontend["static_base_url"], config.frontend["extra_html"], csshtml, customcss, jshtml, customjs, config.js_config(), ui["class"], div) +""" % (ui["doctype"], config.frontend["app_title"], config.frontend["static_base_url"], config.frontend["extra_html"], csshtml, customcss, jshtml, customjs, config.js_config(), ui["class"], div, flash) def main(outputdir=".", produce_debug=True): p = os.path.join(outputdir, "static") diff --git a/iris.conf.example b/iris.conf.example index 0732925..1231411 100644 --- a/iris.conf.example +++ b/iris.conf.example @@ -94,6 +94,30 @@ webirc_mode: webirc_password: fish +# FRONTEND IRC CONNECTION OPTIONS +# These options provide the needed information for the frontend to connect to +# the IRC server via tcp using a flash plugin. +# They require a backend restart and a rerun of compile.py to update. +[flash] + +# SERVER: Hostname (or IP address) of IRC server to connect to. +server: irc.myserver.com + +# PORT: Port of IRC server to connect to. +port: 6667 + +# XMLPORT: Port of IRC servers flash policy daemon +xmlport: 8430 + +# FRONTEND IRC CONNECTION OPTIONS +# These options provide the needed information for the frontend to connect to +# the IRC server via websocket +# They require a backend restart and a rerun of compile.py to update. +[websocket] + +# URL: URL of IRC server to connect to. +url: ws://irc.myserver.com/ + # ATHEME ENGINE OPTIONS # These options control communication with Atheme by the Atheme engine backend, @@ -221,6 +245,10 @@ static_base_url: / # Iris instances on the same host. dynamic_base_url: / +# CONNECTIONS: What order to attempt methods of connection in +# space seperated list of methods +# valid values: ajax flash websocket +connections: ajax # ATHEME INTEGRATION OPTIONS diff --git a/js/base64.js b/js/base64.js new file mode 100644 index 0000000..e2a6911 --- /dev/null +++ b/js/base64.js @@ -0,0 +1,62 @@ +/* Base64 polyfill from https://github.com/davidchambers/Base64.js/ */ + +;(function () { + + var object = typeof exports != 'undefined' ? exports : this; // #8: web workers + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + function InvalidCharacterError(message) { + this.message = message; + } + InvalidCharacterError.prototype = new Error; + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + + // encoder + // [https://gist.github.com/999166] by [https://github.com/nignag] + object.btoa || ( + object.btoa = function (input) { + for ( + // initialize result and counter + var block, charCode, idx = 0, map = chars, output = ''; + // if the next input index does not exist: + // change the mapping table to "=" + // check if d has no fractional digits + input.charAt(idx | 0) || (map = '=', idx % 1); + // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = input.charCodeAt(idx += 3/4); + if (charCode > 0xFF) { + throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); + } + block = block << 8 | charCode; + } + return output; + }); + + // decoder + // [https://gist.github.com/1020396] by [https://github.com/atk] + object.atob || ( + object.atob = function (input) { + input = input.replace(/=+$/, '') + if (input.length % 4 == 1) { + throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); + } + for ( + // initialize result and counters + var bc = 0, bs, buffer, idx = 0, output = ''; + // get next character + buffer = input.charAt(idx++); + // character found in table? initialize bit storage and add its ascii value; + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + // and if not first of each 4 characters, + // convert the first 8 bits to one ascii character + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + // try to find character in table (0-63, not found => -1) + buffer = chars.indexOf(buffer); + } + return output; + }); + +}()); diff --git a/js/irc/baseircclient.js b/js/irc/baseircclient.js index a735489..65711d6 100644 --- a/js/irc/baseircclient.js +++ b/js/irc/baseircclient.js @@ -22,27 +22,54 @@ qwebirc.irc.BaseIRCClient = new Class({ this.toIRCLower = qwebirc.irc.RFC1459toIRCLower; this.nickname = connOptions.nickname; + this.authUser = connOptions.authUser; + this.authSecret = connOptions.authSecret; this.lowerNickname = this.toIRCLower(this.nickname); this.__signedOn = false; + this.__connected = false; this.caps = {}; + this.sasl_timeout = false; this.pmodes = {b: qwebirc.irc.PMODE_LIST, l: qwebirc.irc.PMODE_SET_ONLY, k: qwebirc.irc.PMODE_SET_UNSET, o: qwebirc.irc.PMODE_SET_UNSET, v: qwebirc.irc.PMODE_SET_UNSET}; this.channels = {} this.nextctcp = 0; - connOptions.initialNickname = this.nickname; - connOptions.onRecv = this.dispatch.bind(this); - this.connection = new qwebirc.irc.IRCConnection(session, connOptions); - - this.send = this.connection.send.bind(this.connection); - this.connect = this.connection.connect.bind(this.connection); - this.disconnect = this.connection.disconnect.bind(this.connection); + this.connections = []; + for(var x = 0; x < conf.frontend.connections.length; x++) { + switch(conf.frontend.connections[x]) { + case "ajax": this.connections.unshift(qwebirc.irc.IRCConnection); break; + case "flash": this.connections.unshift(qwebirc.irc.FlashConnection); break; + case "websocket": this.connections.unshift(qwebirc.irc.WSConnection); break; + } + } this.setupGenericErrors(); }, + send: function(data) { + return this.connection.send(data); + }, + connect: function() { + this.tryConnect(); + }, + disconnect: function() { + this.connection.disconnect(); + }, + tryConnect: function() { + var options = {}; + var Connection = this.connections.pop(); + if(Connection) { + options.initialNickname = this.nickname; + options.onRecv = this.dispatch.bind(this); + this.connection = new Connection(this.session, options); + this.connection.connect(); + } else { + this.disconnected("Unable to connect") + } + }, dispatch: function(data) { var message = data[0]; if(message == "connect") { + this.__connected = true; this.connected(); } else if(message == "disconnect") { if(data.length == 0) { @@ -50,12 +77,33 @@ qwebirc.irc.BaseIRCClient = new Class({ } else { this.disconnected(data[1]); } - this.disconnect(); + if(this.__connected) { + this.disconnect(); + } else { + this.tryConnect(); + } } else if(message == "c") { - var command = data[1].toUpperCase(); + var line = data[1]; + var command = ""; + var prefix = ""; + var params = []; + var trailing = ""; + + if (line[0] == ":") { + var index = line.indexOf(" "); + prefix = line.substring(1, index); + line = line.substring(index + 1); + } + if (line.indexOf(" :") != -1) { + var index = line.indexOf(" :"); + trailing = line.substring(index + 2); + params = line.substring(0, index).split(" "); + params.push(trailing); + } else { + params = line.split(" "); + } + command = params.splice(0, 1)[0].toUpperCase(); - var prefix = data[2]; - var sl = data[3]; var n = qwebirc.irc.Numerics[command]; var x = n; @@ -65,11 +113,11 @@ qwebirc.irc.BaseIRCClient = new Class({ var o = this["irc_" + n]; if(o) { - var r = o.run([prefix, sl], this); + var r = o.run([prefix, params], this); if(!r) - this.rawNumeric(command, prefix, sl); + this.rawNumeric(command, prefix, params); } else { - this.rawNumeric(command, prefix, sl); + this.rawNumeric(command, prefix, params); } } }, @@ -102,22 +150,63 @@ qwebirc.irc.BaseIRCClient = new Class({ } }, irc_AUTHENTICATE: function(prefix, params) { - /* Silently hide. */ + this.send("AUTHENTICATE "+btoa([this.authUser, this.authUser, this.authSecret].join('\0'))); return true; }, + irc_saslFinished: function(prefix, params) { + this.send("CAP END"); + $clear(this.sasl_timeout); + return false; + }, + __saslTimeout: function() { + this.send("CAP END"); + }, irc_CAP: function(prefix, params) { - if(params[1] == "ACK") { - var capslist = []; - if (params[2] == "*") - capslist = params[3].split(" "); - else - capslist = params[2].split(" "); + var caplist; + switch(params[1]) { + case "ACK": + if (params[2] == "*") { + caplist = params[3].split(" "); + } else { + caplist = params[2].split(" "); + } - var i; - for (i = 0; i < capslist.length; i++) { - this.caps[capslist[i]] = true; - if (capslist[i] == "sasl") - this.rawNumeric("AUTHENTICATE", prefix, ["*", "Attempting SASL authentication..."]); + for (i = 0; i < caplist.length; i++) + this.caps[caplist[i]] = true + + if (params[2] != "*") { + if(this.caps.sasl && this.authUser) { + this.send("AUTHENTICATE "+conf.atheme.sasl_type); + this.sasl_timeout = this.__saslTimeout.delay(15000, this); + } else { + this.send("CAP END"); + } + } + break; + case "NAK": + this.send("CAP END"); + break; + case "LS": + if (params[2] == "*") { + caplist = params[3].split(" "); + } else { + caplist = params[2].split(" "); + } + + for (i = 0; i < caplist.length; i++) { + if (caplist[i] == "sasl") + this.caps[caplist[i]] = false; + if (caplist[i] == "multi-prefix") + this.caps[caplist[i]] = false; + } + + if (params[2] != "*") { + caplist = Object.keys(this.caps); + if(caplist.length) { + this.send("CAP REQ :"+caplist.join(" ")); + } else { + this.send("CAP END"); + } } } @@ -514,6 +603,9 @@ qwebirc.irc.BaseIRCClient = new Class({ this.irc_ERR_CHANOPPRIVSNEEDED = this.irc_ERR_CANNOTSENDTOCHAN = this.irc_genericError; this.irc_ERR_NOSUCHNICK = this.irc_genericQueryError; this.irc_ERR_NICKNAMEINUSE = this.irc_ERR_UNAVAILRESOURCE = this.irc_genericNickInUse; + this.irc_RPL_LOGGEDIN = this.irc_ERR_NICKLOCKED = this.irc_saslFinished; + this.irc_ERR_SASLFAIL = this.irc_ERR_SASLTOOLONG = this.irc_saslFinished; + this.irc_ERR_SASLABORTED = this.irc_ERR_SASLALREADY = this.irc_saslFinished; return true; }, irc_RPL_AWAY: function(prefix, params) { diff --git a/js/irc/flashconnection.js b/js/irc/flashconnection.js new file mode 100644 index 0000000..b69179a --- /dev/null +++ b/js/irc/flashconnection.js @@ -0,0 +1,113 @@ + +qwebirc.irc.FlashConnection = new Class({ + Implements: [Events, Options], + options: { + initialNickname: "ircconnX", + server: "irc.example.com", + port: 6667, + xmlport: 8430, + timeout: 45000, + maxRetries: 5, + serverPassword: null + }, + initialize: function(session, options) { + this.setOptions(options, conf.flash); + }, + connect: function() { + this.buffer = []; + if(!FlashSocket.connect) { + this.fireEvent("recv", [["disconnect", "No Flash support"]]); + return; + } + FlashSocket.state = this.__state.bind(this); + FlashSocket.connect(this.options.server, this.options.port, this.options.xmlport); + }, + connected: function() { + this.send("CAP LS"); + this.send("USER "+this.options.initialNickname+" 0 * :qwebirc"); + if(this.options.serverPassword) + this.send("PASS :"+this.options.serverPassword); + this.send("NICK "+this.options.initialNickname); + this.fireEvent("recv", [["connect"]]); + }, + disconnect: function() { + FlashSocket.disconnect(); + }, + disconnected: function(reason) { + reason = reason || "Connection Closed"; + this.fireEvent("recv", [["disconnect", reason]]); + }, + send: function(data, synchronous) { + FlashSocket.write(String(data)+"\r\n"); + return true; + }, + recv: function(data) { + var LF = 10; + var buffer = this.buffer.concat(data); + var i = buffer.indexOf(LF); + while(i != -1) { + var msg = buffer.splice(0, i+1); + msg.pop(); //LF + msg.pop(); //CR + this.fireEvent("recv", [["c", this.decode(msg)]]); + i = buffer.indexOf(LF); + } + this.buffer = buffer; + }, + decode: function(buffer) { + var replace = 65533; //U+FFFD 'REPLACEMENT CHARACTER' + var points = []; + var i = 0; + while(i < buffer.length) { + var len = 0; + var point = 0; + if ((buffer[i] & 0x80) == 0x00) { + point = buffer[i++] + } else if((buffer[i] & 0xE0) == 0xC0) { + len = 1; + point = (buffer[i++] & 0x1F); + } else if((buffer[i] & 0xF0) == 0xE0) { + len = 2; + point = (buffer[i++] & 0x0F) + } else if((buffer[i] & 0xF8) == 0xF0) { + len = 3; + point = (buffer[i++] & 0x07) + } else { + point = replace; + i++; + } + for(x = 0; x < len && i < buffer.length; x++) { + var octet = buffer[i++]; + if((octet & 0xC0) != 0x80) + break; + point = (point << 6) | (octet & 0x3F); + } + /* Prevent ascii being snuck past in unicode */ + if(len != 0 && point < 0x80) + point = replace; + /* Replace partial characters */ + if(x != len) + point = replace; + + if(point >= 0x10000) { + point -= 0x10000; + points.push((point >> 10) + 0xD800); + points.push((point % 0x400) + 0xDC00); + } else { + points.push(point); + } + } + return String.fromCharCode.apply(null, points); + }, + __state: function(state, msg) { + if(state == 1 /* OPEN */) + this.connected(); + if(state == 3 /* CLOSED */) + this.disconnected(); + if(state == 4 /* ERROR */) + this.disconnected(msg); + if(state == 5 /* MESSAGE */) { + this.recv(JSON.parse(msg)); + } + } +}); diff --git a/js/irc/ircclient.js b/js/irc/ircclient.js index a71fd6f..ef66ce1 100644 --- a/js/irc/ircclient.js +++ b/js/irc/ircclient.js @@ -16,7 +16,6 @@ qwebirc.irc.IRCClient = new Class({ this.lastNicks = []; this.inviteChanList = []; - this.activeTimers = {}; this.tracker = new qwebirc.irc.IRCTracker(this); }, @@ -401,7 +400,6 @@ qwebirc.irc.IRCClient = new Class({ __joinInvited: function() { this.exec("/JOIN " + this.inviteChanList.join(",")); this.inviteChanList = []; - delete this.activeTimers["serviceInvite"]; }, userInvite: function(user, channel) { var nick = user.hostToNick(); @@ -504,14 +502,6 @@ qwebirc.irc.IRCClient = new Class({ this.send("QUIT :" + message, true); this.disconnect(); }, - disconnect: function() { - for(var k in this.activeTimers) { - this.activeTimers[k].cancel(); - }; - this.activeTimers = {}; - - this.parent(); - }, awayMessage: function(nick, message) { this.newQueryLine(nick, "AWAY", {"n": nick, "m": message}, true); }, diff --git a/js/irc/ircconnection.js b/js/irc/ircconnection.js index 710a039..50dd8d9 100644 --- a/js/irc/ircconnection.js +++ b/js/irc/ircconnection.js @@ -11,9 +11,7 @@ qwebirc.irc.IRCConnection = new Class({ floodReset: 5000, errorAlert: true, maxRetries: 5, - serverPassword: null, - authUser: null, - authSecret: null + serverPassword: null }, initialize: function(session, options) { this.session = session; @@ -256,10 +254,6 @@ qwebirc.irc.IRCConnection = new Class({ var postdata = "nick=" + encodeURIComponent(this.initialNickname); if($defined(this.options.serverPassword)) postdata+="&password=" + encodeURIComponent(this.options.serverPassword); - if($defined(this.options.authUser) && $defined(this.options.authSecret)) { - postdata+="&authUser=" + encodeURIComponent(this.options.authUser); - postdata+="&authSecret=" + encodeURIComponent(this.options.authSecret); - } r.send(postdata); }, diff --git a/js/irc/numerics.js b/js/irc/numerics.js index b44d2f3..6676218 100644 --- a/js/irc/numerics.js +++ b/js/irc/numerics.js @@ -32,5 +32,14 @@ qwebirc.irc.Numerics = { "305": "RPL_UNAWAY", "306": "RPL_NOWAWAY", "324": "RPL_CHANNELMODEIS", - "329": "RPL_CREATIONTIME" + "329": "RPL_CREATIONTIME", + "900": "RPL_LOGGEDIN", + "901": "RPL_LOGGEDOUT", + "902": "ERR_NICKLOCKED", + "903": "RPL_SASLSUCCESS", + "904": "ERR_SASLFAIL", + "905": "ERR_SASLTOOLONG", + "906": "ERR_SASLABORTED", + "907": "ERR_SASLALREADY", + "908": "RPL_SASLMECHS" }; diff --git a/js/irc/wsconnection.js b/js/irc/wsconnection.js new file mode 100644 index 0000000..1d3439b --- /dev/null +++ b/js/irc/wsconnection.js @@ -0,0 +1,45 @@ + +qwebirc.irc.WSConnection = new Class({ + Implements: [Events, Options], + options: { + initialNickname: "ircconnX", + url: "ws://irc.example.com/", + timeout: 45000, + maxRetries: 5, + serverPassword: null + }, + initialize: function(session, options) { + this.setOptions(options, conf.websocket); + }, + connect: function() { + if(!window.WebSocket) { + this.fireEvent("recv", [["disconnect", "No WebSocket Support"]]); + return; + } + this.socket = new WebSocket(this.options.url, "irc"); + this.socket.onopen = this.connected.bind(this); + this.socket.onclose = this.disconnected.bind(this); + this.socket.onmessage = this.recv.bind(this); + }, + connected: function(e) { + this.send("CAP LS"); + this.send("USER "+this.options.initialNickname+" 0 * :qwebirc"); + if(this.options.serverPassword) + this.send("PASS :"+this.options.serverPassword); + this.send("NICK "+this.options.initialNickname); + this.fireEvent("recv", [["connect"]]); + }, + disconnect: function() { + this.socket.close(); + }, + disconnected: function(e) { + this.fireEvent("recv", [["disconnect", e.reason ? e.reason : "Unknown reason"]]); + }, + send: function(data, synchronous) { + this.socket.send(String(data)); + return true; + }, + recv: function recv(message) { + this.fireEvent("recv", [["c", message.data]]); + } +}); diff --git a/js/jslib.js b/js/jslib.js index dfaccb6..0753332 100644 --- a/js/jslib.js +++ b/js/jslib.js @@ -86,20 +86,6 @@ qwebirc.util.MonthsOfYear = { 11: "Dec" }; -qwebirc.util.NBSPCreate = function(text, element) { - var e = text.split(" "); - for(var i=0;i + is released under the MIT License +*/ + +var swfobject = function() { + + var UNDEF = "undefined", + OBJECT = "object", + SHOCKWAVE_FLASH = "Shockwave Flash", + SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash", + FLASH_MIME_TYPE = "application/x-shockwave-flash", + EXPRESS_INSTALL_ID = "SWFObjectExprInst", + ON_READY_STATE_CHANGE = "onreadystatechange", + + win = window, + doc = document, + nav = navigator, + + plugin = false, + domLoadFnArr = [main], + regObjArr = [], + objIdArr = [], + listenersArr = [], + storedAltContent, + storedAltContentId, + storedCallbackFn, + storedCallbackObj, + isDomLoaded = false, + isExpressInstallActive = false, + dynamicStylesheet, + dynamicStylesheetMedia, + autoHideShow = true, + + /* Centralized function for browser feature detection + - User agent string detection is only used when no good alternative is possible + - Is executed directly for optimal performance + */ + ua = function() { + var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF, + u = nav.userAgent.toLowerCase(), + p = nav.platform.toLowerCase(), + windows = p ? /win/.test(p) : /win/.test(u), + mac = p ? /mac/.test(p) : /mac/.test(u), + webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit + ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html + playerVersion = [0,0,0], + d = null; + if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) { + d = nav.plugins[SHOCKWAVE_FLASH].description; + if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+ + plugin = true; + ie = false; // cascaded feature detection for Internet Explorer + d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); + playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10); + playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10); + playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0; + } + } + else if (typeof win.ActiveXObject != UNDEF) { + try { + var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); + if (a) { // a will return null when ActiveX is disabled + d = a.GetVariable("$version"); + if (d) { + ie = true; // cascaded feature detection for Internet Explorer + d = d.split(" ")[1].split(","); + playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + } + } + catch(e) {} + } + return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac }; + }(), + + /* Cross-browser onDomLoad + - Will fire an event as soon as the DOM of a web page is loaded + - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/ + - Regular onload serves as fallback + */ + onDomLoad = function() { + if (!ua.w3) { return; } + if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically + callDomLoadFunctions(); + } + if (!isDomLoaded) { + if (typeof doc.addEventListener != UNDEF) { + doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false); + } + if (ua.ie && ua.win) { + doc.attachEvent(ON_READY_STATE_CHANGE, function() { + if (doc.readyState == "complete") { + doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee); + callDomLoadFunctions(); + } + }); + if (win == top) { // if not inside an iframe + (function(){ + if (isDomLoaded) { return; } + try { + doc.documentElement.doScroll("left"); + } + catch(e) { + setTimeout(arguments.callee, 0); + return; + } + callDomLoadFunctions(); + })(); + } + } + if (ua.wk) { + (function(){ + if (isDomLoaded) { return; } + if (!/loaded|complete/.test(doc.readyState)) { + setTimeout(arguments.callee, 0); + return; + } + callDomLoadFunctions(); + })(); + } + addLoadEvent(callDomLoadFunctions); + } + }(); + + function callDomLoadFunctions() { + if (isDomLoaded) { return; } + try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early + var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span")); + t.parentNode.removeChild(t); + } + catch (e) { return; } + isDomLoaded = true; + var dl = domLoadFnArr.length; + for (var i = 0; i < dl; i++) { + domLoadFnArr[i](); + } + } + + function addDomLoadEvent(fn) { + if (isDomLoaded) { + fn(); + } + else { + domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+ + } + } + + /* Cross-browser onload + - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/ + - Will fire an event as soon as a web page including all of its assets are loaded + */ + function addLoadEvent(fn) { + if (typeof win.addEventListener != UNDEF) { + win.addEventListener("load", fn, false); + } + else if (typeof doc.addEventListener != UNDEF) { + doc.addEventListener("load", fn, false); + } + else if (typeof win.attachEvent != UNDEF) { + addListener(win, "onload", fn); + } + else if (typeof win.onload == "function") { + var fnOld = win.onload; + win.onload = function() { + fnOld(); + fn(); + }; + } + else { + win.onload = fn; + } + } + + /* Main function + - Will preferably execute onDomLoad, otherwise onload (as a fallback) + */ + function main() { + if (plugin) { + testPlayerVersion(); + } + else { + matchVersions(); + } + } + + /* Detect the Flash Player version for non-Internet Explorer browsers + - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description: + a. Both release and build numbers can be detected + b. Avoid wrong descriptions by corrupt installers provided by Adobe + c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports + - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available + */ + function testPlayerVersion() { + var b = doc.getElementsByTagName("body")[0]; + var o = createElement(OBJECT); + o.setAttribute("type", FLASH_MIME_TYPE); + var t = b.appendChild(o); + if (t) { + var counter = 0; + (function(){ + if (typeof t.GetVariable != UNDEF) { + var d = t.GetVariable("$version"); + if (d) { + d = d.split(" ")[1].split(","); + ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; + } + } + else if (counter < 10) { + counter++; + setTimeout(arguments.callee, 10); + return; + } + b.removeChild(o); + t = null; + matchVersions(); + })(); + } + else { + matchVersions(); + } + } + + /* Perform Flash Player and SWF version matching; static publishing only + */ + function matchVersions() { + var rl = regObjArr.length; + if (rl > 0) { + for (var i = 0; i < rl; i++) { // for each registered object element + var id = regObjArr[i].id; + var cb = regObjArr[i].callbackFn; + var cbObj = {success:false, id:id}; + if (ua.pv[0] > 0) { + var obj = getElementById(id); + if (obj) { + if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match! + setVisibility(id, true); + if (cb) { + cbObj.success = true; + cbObj.ref = getObjectById(id); + cb(cbObj); + } + } + else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported + var att = {}; + att.data = regObjArr[i].expressInstall; + att.width = obj.getAttribute("width") || "0"; + att.height = obj.getAttribute("height") || "0"; + if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); } + if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); } + // parse HTML object param element's name-value pairs + var par = {}; + var p = obj.getElementsByTagName("param"); + var pl = p.length; + for (var j = 0; j < pl; j++) { + if (p[j].getAttribute("name").toLowerCase() != "movie") { + par[p[j].getAttribute("name")] = p[j].getAttribute("value"); + } + } + showExpressInstall(att, par, id, cb); + } + else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF + displayAltContent(obj); + if (cb) { cb(cbObj); } + } + } + } + else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content) + setVisibility(id, true); + if (cb) { + var o = getObjectById(id); // test whether there is an HTML object element or not + if (o && typeof o.SetVariable != UNDEF) { + cbObj.success = true; + cbObj.ref = o; + } + cb(cbObj); + } + } + } + } + } + + function getObjectById(objectIdStr) { + var r = null; + var o = getElementById(objectIdStr); + if (o && o.nodeName == "OBJECT") { + if (typeof o.SetVariable != UNDEF) { + r = o; + } + else { + var n = o.getElementsByTagName(OBJECT)[0]; + if (n) { + r = n; + } + } + } + return r; + } + + /* Requirements for Adobe Express Install + - only one instance can be active at a time + - fp 6.0.65 or higher + - Win/Mac OS only + - no Webkit engines older than version 312 + */ + function canExpressInstall() { + return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); + } + + /* Show the Adobe Express Install dialog + - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 + */ + function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { + isExpressInstallActive = true; + storedCallbackFn = callbackFn || null; + storedCallbackObj = {success:false, id:replaceElemIdStr}; + var obj = getElementById(replaceElemIdStr); + if (obj) { + if (obj.nodeName == "OBJECT") { // static publishing + storedAltContent = abstractAltContent(obj); + storedAltContentId = null; + } + else { // dynamic publishing + storedAltContent = obj; + storedAltContentId = replaceElemIdStr; + } + att.id = EXPRESS_INSTALL_ID; + if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; } + if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; } + doc.title = doc.title.slice(0, 47) + " - Flash Player Installation"; + var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn", + fv = "MMredirectURL=" + encodeURI(win.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title; + if (typeof par.flashvars != UNDEF) { + par.flashvars += "&" + fv; + } + else { + par.flashvars = fv; + } + // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, + // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work + if (ua.ie && ua.win && obj.readyState != 4) { + var newObj = createElement("div"); + replaceElemIdStr += "SWFObjectNew"; + newObj.setAttribute("id", replaceElemIdStr); + obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + obj.parentNode.removeChild(obj); + } + else { + setTimeout(arguments.callee, 10); + } + })(); + } + createSWF(att, par, replaceElemIdStr); + } + } + + /* Functions to abstract and display alternative content + */ + function displayAltContent(obj) { + if (ua.ie && ua.win && obj.readyState != 4) { + // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, + // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work + var el = createElement("div"); + obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content + el.parentNode.replaceChild(abstractAltContent(obj), el); + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + obj.parentNode.removeChild(obj); + } + else { + setTimeout(arguments.callee, 10); + } + })(); + } + else { + obj.parentNode.replaceChild(abstractAltContent(obj), obj); + } + } + + function abstractAltContent(obj) { + var ac = createElement("div"); + if (ua.win && ua.ie) { + ac.innerHTML = obj.innerHTML; + } + else { + var nestedObj = obj.getElementsByTagName(OBJECT)[0]; + if (nestedObj) { + var c = nestedObj.childNodes; + if (c) { + var cl = c.length; + for (var i = 0; i < cl; i++) { + if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) { + ac.appendChild(c[i].cloneNode(true)); + } + } + } + } + } + return ac; + } + + /* Cross-browser dynamic SWF creation + */ + function createSWF(attObj, parObj, id) { + var r, el = getElementById(id); + if (ua.wk && ua.wk < 312) { return r; } + if (el) { + if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content + attObj.id = id; + } + if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML + var att = ""; + for (var i in attObj) { + if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries + if (i.toLowerCase() == "data") { + parObj.movie = attObj[i]; + } + else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword + att += ' class="' + attObj[i] + '"'; + } + else if (i.toLowerCase() != "classid") { + att += ' ' + i + '="' + attObj[i] + '"'; + } + } + } + var par = ""; + for (var j in parObj) { + if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries + par += ''; + } + } + el.outerHTML = '' + par + ''; + objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only) + r = getElementById(attObj.id); + } + else { // well-behaving browsers + var o = createElement(OBJECT); + o.setAttribute("type", FLASH_MIME_TYPE); + for (var m in attObj) { + if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries + if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword + o.setAttribute("class", attObj[m]); + } + else if (m.toLowerCase() != "classid") { // filter out IE specific attribute + o.setAttribute(m, attObj[m]); + } + } + } + for (var n in parObj) { + if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element + createObjParam(o, n, parObj[n]); + } + } + el.parentNode.replaceChild(o, el); + r = o; + } + } + return r; + } + + function createObjParam(el, pName, pValue) { + var p = createElement("param"); + p.setAttribute("name", pName); + p.setAttribute("value", pValue); + el.appendChild(p); + } + + /* Cross-browser SWF removal + - Especially needed to safely and completely remove a SWF in Internet Explorer + */ + function removeSWF(id) { + var obj = getElementById(id); + if (obj && obj.nodeName == "OBJECT") { + if (ua.ie && ua.win) { + obj.style.display = "none"; + (function(){ + if (obj.readyState == 4) { + removeObjectInIE(id); + } + else { + setTimeout(arguments.callee, 10); + } + })(); + } + else { + obj.parentNode.removeChild(obj); + } + } + } + + function removeObjectInIE(id) { + var obj = getElementById(id); + if (obj) { + for (var i in obj) { + if (typeof obj[i] == "function") { + obj[i] = null; + } + } + obj.parentNode.removeChild(obj); + } + } + + /* Functions to optimize JavaScript compression + */ + function getElementById(id) { + var el = null; + try { + el = doc.getElementById(id); + } + catch (e) {} + return el; + } + + function createElement(el) { + return doc.createElement(el); + } + + /* Updated attachEvent function for Internet Explorer + - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks + */ + function addListener(target, eventType, fn) { + target.attachEvent(eventType, fn); + listenersArr[listenersArr.length] = [target, eventType, fn]; + } + + /* Flash Player and SWF content version matching + */ + function hasPlayerVersion(rv) { + var pv = ua.pv, v = rv.split("."); + v[0] = parseInt(v[0], 10); + v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0" + v[2] = parseInt(v[2], 10) || 0; + return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; + } + + /* Cross-browser dynamic CSS creation + - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php + */ + function createCSS(sel, decl, media, newStyle) { + if (ua.ie && ua.mac) { return; } + var h = doc.getElementsByTagName("head")[0]; + if (!h) { return; } // to also support badly authored HTML pages that lack a head element + var m = (media && typeof media == "string") ? media : "screen"; + if (newStyle) { + dynamicStylesheet = null; + dynamicStylesheetMedia = null; + } + if (!dynamicStylesheet || dynamicStylesheetMedia != m) { + // create dynamic stylesheet + get a global reference to it + var s = createElement("style"); + s.setAttribute("type", "text/css"); + s.setAttribute("media", m); + dynamicStylesheet = h.appendChild(s); + if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) { + dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; + } + dynamicStylesheetMedia = m; + } + // add style rule + if (ua.ie && ua.win) { + if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) { + dynamicStylesheet.addRule(sel, decl); + } + } + else { + if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) { + dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}")); + } + } + } + + function setVisibility(id, isVisible) { + if (!autoHideShow) { return; } + var v = isVisible ? "visible" : "hidden"; + if (isDomLoaded && getElementById(id)) { + getElementById(id).style.visibility = v; + } + else { + createCSS("#" + id, "visibility:" + v); + } + } + + /* Filter to avoid XSS attacks + */ + function urlEncodeIfNecessary(s) { + var regex = /[\\\"<>\.;]/; + var hasBadChars = regex.exec(s) != null; + return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s; + } + + /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only) + */ + var cleanup = function() { + if (ua.ie && ua.win) { + window.attachEvent("onunload", function() { + // remove listeners to avoid memory leaks + var ll = listenersArr.length; + for (var i = 0; i < ll; i++) { + listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]); + } + // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect + var il = objIdArr.length; + for (var j = 0; j < il; j++) { + removeSWF(objIdArr[j]); + } + // cleanup library's main closures to avoid memory leaks + for (var k in ua) { + ua[k] = null; + } + ua = null; + for (var l in swfobject) { + swfobject[l] = null; + } + swfobject = null; + }); + } + }(); + + return { + /* Public API + - Reference: http://code.google.com/p/swfobject/wiki/documentation + */ + registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) { + if (ua.w3 && objectIdStr && swfVersionStr) { + var regObj = {}; + regObj.id = objectIdStr; + regObj.swfVersion = swfVersionStr; + regObj.expressInstall = xiSwfUrlStr; + regObj.callbackFn = callbackFn; + regObjArr[regObjArr.length] = regObj; + setVisibility(objectIdStr, false); + } + else if (callbackFn) { + callbackFn({success:false, id:objectIdStr}); + } + }, + + getObjectById: function(objectIdStr) { + if (ua.w3) { + return getObjectById(objectIdStr); + } + }, + + embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) { + var callbackObj = {success:false, id:replaceElemIdStr}; + if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) { + setVisibility(replaceElemIdStr, false); + addDomLoadEvent(function() { + widthStr += ""; // auto-convert to string + heightStr += ""; + var att = {}; + if (attObj && typeof attObj === OBJECT) { + for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs + att[i] = attObj[i]; + } + } + att.data = swfUrlStr; + att.width = widthStr; + att.height = heightStr; + var par = {}; + if (parObj && typeof parObj === OBJECT) { + for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs + par[j] = parObj[j]; + } + } + if (flashvarsObj && typeof flashvarsObj === OBJECT) { + for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs + if (typeof par.flashvars != UNDEF) { + par.flashvars += "&" + k + "=" + flashvarsObj[k]; + } + else { + par.flashvars = k + "=" + flashvarsObj[k]; + } + } + } + if (hasPlayerVersion(swfVersionStr)) { // create SWF + var obj = createSWF(att, par, replaceElemIdStr); + if (att.id == replaceElemIdStr) { + setVisibility(replaceElemIdStr, true); + } + callbackObj.success = true; + callbackObj.ref = obj; + } + else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install + att.data = xiSwfUrlStr; + showExpressInstall(att, par, replaceElemIdStr, callbackFn); + return; + } + else { // show alternative content + setVisibility(replaceElemIdStr, true); + } + if (callbackFn) { callbackFn(callbackObj); } + }); + } + else if (callbackFn) { callbackFn(callbackObj); } + }, + + switchOffAutoHideShow: function() { + autoHideShow = false; + }, + + ua: ua, + + getFlashPlayerVersion: function() { + return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] }; + }, + + hasFlashPlayerVersion: hasPlayerVersion, + + createSWF: function(attObj, parObj, replaceElemIdStr) { + if (ua.w3) { + return createSWF(attObj, parObj, replaceElemIdStr); + } + else { + return undefined; + } + }, + + showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) { + if (ua.w3 && canExpressInstall()) { + showExpressInstall(att, par, replaceElemIdStr, callbackFn); + } + }, + + removeSWF: function(objElemIdStr) { + if (ua.w3) { + removeSWF(objElemIdStr); + } + }, + + createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) { + if (ua.w3) { + createCSS(selStr, declStr, mediaStr, newStyleBoolean); + } + }, + + addDomLoadEvent: addDomLoadEvent, + + addLoadEvent: addLoadEvent, + + getQueryParamValue: function(param) { + var q = doc.location.search || doc.location.hash; + if (q) { + if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark + if (param == null) { + return urlEncodeIfNecessary(q); + } + var pairs = q.split("&"); + for (var i = 0; i < pairs.length; i++) { + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { + return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1))); + } + } + } + return ""; + }, + + // For internal usage only + expressInstallCallback: function() { + if (isExpressInstallActive) { + var obj = getElementById(EXPRESS_INSTALL_ID); + if (obj && storedAltContent) { + obj.parentNode.replaceChild(storedAltContent, obj); + if (storedAltContentId) { + setVisibility(storedAltContentId, true); + if (ua.ie && ua.win) { storedAltContent.style.display = "block"; } + } + if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); } + } + isExpressInstallActive = false; + } + } + }; +}(); diff --git a/js/ui/url.js b/js/ui/url.js index 9833233..ad6a5b6 100644 --- a/js/ui/url.js +++ b/js/ui/url.js @@ -24,8 +24,17 @@ qwebirc.ui.urlificate = function(session, element, text) { }; var appendText = function(text) { - addedText.push(text); - qwebirc.util.NBSPCreate(text, element); + var e = text.split(/( )/); + for(var i=0;i= 400): - self.write("AUTHENTICATE " + authtext[0:400]) - authtext = authtext[400:] - if (len(authtext) != 0): - self.write("AUTHENTICATE " + authtext) - else: - self.write("AUTHENTICATE +") - - # Handle SASL result messages. - # End CAP after one of these, unless an acknowledgement message is still - # waiting. - if (command in ["903", "904", "905","906","907"]): - if (self.saslauth): - self.saslauth = False - if (not self.saslauth): - self.write("CAP END") - if command == "001": self.registered = True self.__nickname = params[0] @@ -130,21 +55,12 @@ def lineReceived(self, line): if nick == self.__nickname: self.__nickname = params[0] - def stopSasl(self): - """Cancels SASL authentication. Useful if Services are absent.""" - if (self.saslauth): - self.saslauth = False - self.write("CAP END") - - # Send an internally-generated failure response to the client. - self.handleCommand("904", "QWebIRC", ["*", "SASL authentication timed out"]) - - def handleCommand(self, command, prefix, params): - self("c", command, prefix, params) - + def handleLine(self, line): + self("c", line) + def __call__(self, *args): self.factory.publisher.event(args) - + def write(self, data): self.transport.write("%s\r\n" % irc.lowQuote(data.encode("utf-8"))) @@ -160,8 +76,6 @@ def connectionMade(self): nick, ident, ip, realname, hostname, pass_ = f["nick"], f["ident"], f["ip"], f["realname"], f["hostname"], f.get("password") self.__nickname = nick self.__perform = f.get("perform") - self.authUser = f.get("authUser") - self.authSecret = f.get("authSecret") if config.irc["webirc_mode"] == "webirc": self.write("WEBIRC %s qwebirc %s %s" % (config.irc["webirc_password"], hostname, ip)) diff --git a/swf/flashsocket.as b/swf/flashsocket.as new file mode 100644 index 0000000..15b818d --- /dev/null +++ b/swf/flashsocket.as @@ -0,0 +1,91 @@ +package +{ + import flash.utils.ByteArray; + import flash.display.MovieClip; + import flash.events.Event; + import flash.events.ErrorEvent; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.events.ProgressEvent; + import flash.net.Socket; + import flash.system.Security; + import flash.external.ExternalInterface; + + public class JsSocket + { + private var id:int; + private var host:String; + private var port:int; + private var xmlport:int; + private var socket:Socket; + + private const CONNECTING:int = 0; + private const OPEN:int = 1; + private const CLOSING:int = 2; + private const CLOSED:int = 3; + private const ERROR:int = 4; + private const MESSAGE:int = 5; + + public function JsSocket():void { + socket = new Socket(); + socket.addEventListener(Event.CONNECT, onConnect); + socket.addEventListener(Event.CLOSE, onDisconnect); + socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + socket.addEventListener(IOErrorEvent.IO_ERROR, onError); + socket.addEventListener(ProgressEvent.SOCKET_DATA, onData); + } + + public function connect(host:String, port:int, xmlport):void { + Security.loadPolicyFile("xmlsocket://"+host+":"+xmlport); + socket.connect(host, port); + ExternalInterface.call("FlashSocket.state", CONNECTING, "CONNECTING"); + } + + public function disconnect():void { + socket.close(); + ExternalInterface.call("FlashSocket.state", CLOSED, ""); + } + + private function write(data:String):void { + socket.writeUTFBytes(data); + socket.flush(); + } + + private function onConnect(e:Event):void { + ExternalInterface.call("FlashSocket.state", OPEN, e.toString()); + } + + private function onDisconnect(e:Event):void { + ExternalInterface.call("FlashSocket.state", CLOSED, e.toString()); + } + + private function onError(e:ErrorEvent):void { + ExternalInterface.call("FlashSocket.state", ERROR, e.toString()); + socket.close(); + } + + private function onData(e:ProgressEvent):void { + var out:String = "["; + out += socket.readUnsignedByte(); + while(socket.bytesAvailable) + out += ","+socket.readUnsignedByte(); + out += "]"; + ExternalInterface.call("FlashSocket.state", MESSAGE, out); + } + + } + + public class Main extends MovieClip + { + private function Main():void { + var socket:JsSocket = new JsSocket(); + flash.system.Security.allowDomain("*"); + flash.system.Security.allowInsecureDomain("*"); + ExternalInterface.marshallExceptions = true; + + ExternalInterface.addCallback("connect", socket.connect); + ExternalInterface.addCallback("disconnect", socket.disconnect); + ExternalInterface.addCallback("write", socket.write); + } + } +}