This demo is an exemple of the implementation of the TCPSockets in APE. Here, APE is used as a middleware, making it the gateway to an IP:Port : IRC.
Check out the Client JavaScript, HTML and ServerSide JavaScript source code of this demo by reading the following :
<link rel="stylesheet" type="text/css" href="./irc.css" /> <script type="text/javaScript" src="../Clients/mootools-core.js"></script> <script type="text/javaScript" src="../Clients/MooTools.js"></script> <script type="text/javaScript" src="../Clients/config.js"></script> <script type="text/javaScript" src="./irc.js"></script> <script type="text/javaScript" src="./apeirc.js"></script> <script type="text/javaScript" src="./channel.js"></script> <script type="text/javascript"> function rand_chars(){ var keylist="abcdefghijklmnopqrstuvwxyz" var temp='' var plength=8; for (i=0;i<plength;i++){ temp+=keylist.charAt(Math.floor(Math.random()*keylist.length)) } return temp; } function setNick(){ var nick = $('nick_input').value.replace(' ', ''); if(nick.length == 0){ alert('Veuillez entrer un login'); }else{ if( irc_client.setNick(nick.substr(0, 15)) ){ var login = $('login'); login.set('tween', {duration: 'short'}); login.tween('opacity', 0); setNick = function(){}; } } } var irc_client = new APE.IrcClient(); window.addEvent('domready', function(){ $('nick_input').focus(); irc_client.load({ 'domain': APE.Config.domain, 'server': APE.Config.server, 'identifier':'ircdemo', 'scripts': APE.Config.scripts, 'complete': function(ape){ irc_client.complete(); } }); }); </script> <div id="ircchat"> <div id="login"> <div class="popup"> <p>Choose a nickname to sign in to IRC on freenode</p> Your nickname : <input maxlength="15" id="nick_input" onkeypress="if (event.keyCode == 13) setNick();" type="text" /> <button onclick="setNick();">Ok</button> </div> </div> <div class="header"> <div class="server">IRC @ freenode</div> <div id="tabs"></div> </div> <div class="chat"> <div class="texte-zone"> <div id="line"> </div> <div class="texte" id="history"></div> </div> <div class="list"> <div class="header"><span id="usr_cnt">0</span> users</span></div> <div class="user-list"> <div id="user-list"></div> </div> </div> <div class="input-zone"> <button id="chatButton" onclick="irc_client.sendClick()">Ok</button> <input type="text" id="chat-input" /> <div class="show-nick">jchavarria_work</div> </div> </div> </div> <div id="ape_master_container"></div>
/* irc.js * This IRC client runs in a web browser using pure JavaScript * Orbited 0.5+ required * * Methods: * connect(hostname, port) * ident(nickname, modes, real_name) * join(channel) * names(channel) * part(channel) * quit(reason) * privmsg(destination, message) * * Callbacks: * Built-in callbacks are onconnect(), onerror(), onresponse(), and onclose() * onerror and onreply are passed numerical reponse codes, see: * http://www.irchelp.org/irchelp/rfc/chapter6.html for a list of IRC response * codes. * * To add callbacks for IRC actions, for instance PRIVMSG, * set onPRIVMSG = function(command) {...you code here...} * See the included IRC demo (/static/demos/irc) for example usage * * Frank Salim (frank.salim@gmail.com) * ©2008 The Orbited Project */ // TODO DRY this by creating a common logging infrastructure (this is also on stomp.js) IRC_DEBUG = false; if (IRC_DEBUG && typeof(Orbited)) { var getIrcLogger = function(name) { var logger = Orbited.getLogger(name); if (!("dir" in logger)) { logger.dir = function() {}; } return logger; } } else if (IRC_DEBUG && typeof(console)) { var getIrcLogger = function(name) { return { debug: function() { var args = Array.prototype.slice.call(arguments); args.unshift(name, ": "); console.debug.apply(console, args); }, dir: function() { console.debug(name, ":"); console.dir.apply(console, arguments); } }; }; } else { var getIrcLogger = function(name) { return { debug: function() {}, dir: function() {} }; }; } IRCClient = function() { var log = getIrcLogger("IRCClient"); var self = this var conn = null var buffer = "" var ENDL = "\r\n" self.onopen = function() {}; self.onconnect = function() {} // Do nothing in default callbacks self.onclose = function() {} self.onerror = function(command) {} self.onresponse = function(command) {} // used for numerical replies self.connect = function(hostname, port) { log.debug("connect"); conn = self._createTransport(); conn.onopen = conn_opened conn.onclose = conn_closed conn.onread = conn_read conn.open(hostname, port) // TODO set onerror. } self._createTransport = function() { return new TCPSocket(); }; self.close = function(code) { log.debug("close: "+code); conn.close(); conn.onopen = null; conn.onclose = null; conn.onread = null; self.onclose(code); } self.ident = function(nickname, modes, real_name) { send("USER", nickname + " " + modes + " :" + real_name) } self.nick = function(nickname) { send("NICK", nickname) } self.join = function(channel) { send("JOIN", channel) } self.names = function(channel) { send("NAMES", channel) } self.ctcp = function(to, cmd, rep) { if(!rep) this.privmsg(to, '\01'+cmd+'\01'); else this.notice(to, '\01'+cmd+'\01'); } self.part = function(channel, reason) { send("PART", channel + " :" + reason) } self.quit = function(reason) { var reason = reason || "leaving"; send("QUIT", ":" + reason) conn.close() } self.reset = function() { conn.reset(); } self.action = function(destination, message) { send('PRIVMSG', destination + ' :\01ACTION ' + message + '\01') } self.notice = function(destination, message) { send('NOTICE', destination + ' :'+message) } self.privmsg = function(destination, message) { send('PRIVMSG', destination + ' :' + message) } // Socket Callbacks var conn_opened = function() { self.onopen() } var conn_closed = function(code) { self.onclose(code) } var conn_read = function(data) { log.debug("data:"); log.debug(data); buffer += data parse_buffer() } // Internal Functions var send = function(type, payload) { log.debug("send: " + payload); conn.send(type + " " + payload + ENDL); }; var parse_buffer= function() { var commands = buffer.split(ENDL); buffer = commands[commands.length-1]; for (var i = 0, l = commands.length - 1; i < l; ++i) { var line = commands[i]; if (line.length > 0) dispatch(line); } }; var parse_command = function(s) { // See http://tools.ietf.org/html/rfc2812#section-2.3 // all the arguments are split by a single space character until // the first ":" character. the ":" marks the start of the last // trailing argument which can contain embeded space characters. var i = s.indexOf(" :"); if (i >= 0) { var args = s.slice(0, i).split(' '); args.push(s.slice(i + 2)); } else { var args = s.split(' '); } // extract the prefix (if there is one). if (args[0].charAt(0) == ":") { var prefix = args.shift().slice(1); } else { var prefix = null; } var command = { prefix: prefix, type: args.shift(), args: args }; log.debug("command:"); log.dir(command); return command; }; var dispatch = function(line) { command = parse_command(line); //console.log('COMMAND',command.type); if (command.type == "PING") { send("PONG", ":" + command.args) } if (!isNaN(parseInt(command.type))) { var error_code = parseInt(command.type) if (error_code > 400) return self.onerror(command) else return self.onresponse(command) } if (command.type == "PRIVMSG") { msg = command.args[1] if (msg.charCodeAt(0) == 1 && msg.charCodeAt(msg.length-1) == 1) { var args = [command.args[0]] var newargs = msg.slice(1, msg.length - 1).split(' ') if (newargs[0] == 'ACTION') { command.type = newargs.shift() } else { command.type = 'CTCP' } for (var i = 0; i < newargs.length; ++i) { args.push(newargs[i]) } command.args = args } } if (typeof(self["on" + command.type]) == "function") { // XXX the user is able to define unknown command handlers, // but cannot send any arbitrary command self["on" + command.type](command); } else { log.debug("unhandled command received: ", command.type); } }; };
/** * This demo uses APE to create a Vitual TCPSocket and * connect to an irc server (Its transparent, but it's the ape server which * connects to the irc server). * * */ var TCPSocket; APE.IrcClient = new Class({ Extends: APE.Client, Implements: Options, irc: null, connected: false, joined: false, currentChannel: null, version: '0.1', options: { container: document.body, /* irc_server: '10.1.0.30', irc_port: 6667, */ irc_server: 'irc.freenode.net', irc_port: 6667, /* irc_server: 'Vancouver.BC.CA.Undernet.org', irc_port: 6667, */ original_channels:['#ape-test','#ape-project'], systemChannel: '@freenode', systemUser: '*System*', helpUser: '*HELP*' }, els: {}, channels: new Hash(), initialize: function(options){ this.currentChannel = this.options.systemChannel; this.setOptions(options); this.joinVirtualChannel(this.options.systemChannel); }, joinVirtualChannel: function(chan, irc, buildTabs, doNotClose){ this.setChannel(chan, new APE.IrcClient.Channel(chan, irc, doNotClose)); if(buildTabs){ this.buildTabs(); } }, joinUserChannel: function(user){ if(!this.hasChannel(user)){ this.joinVirtualChannel(user, this.irc, true, true); } }, complete: function(){ this.core.start({name:rand_chars()}); this.onRaw('login', this.initPlayground); }, initPlayground: function(){ this.els.chatZone = $('history'); this.els.tabs = $('tabs'); this.els.input = $('chat-input'); this.els.userList = $('user-list'); this.els.userCnt = $('usr_cnt'); //Adding special events this.els.input.addEvent('keydown', this.sendKey.bindWithEvent(this)); //TCPSocket implementation TCPSocket = this.core.TCPSocket; //IRC events this.irc = new IRCClient(); this.irc.onopen = this.onopen.bind(this); this.irc.onclose = this.onclose.bind(this); this.irc.onTOPIC = this.onTOPIC.bind(this); this.irc.onNICK = this.onNICK.bind(this); this.irc.onJOIN = this.onJOIN.bind(this); this.irc.onQUIT = this.onQUIT.bind(this); this.irc.onPART = this.onPART.bind(this); this.irc.onACTION = this.onACTION.bind(this); this.irc.onCTCP = this.onCTCP.bind(this); this.irc.onNOTICE = this.onNOTICE.bind(this); this.irc.onPRIVMSG = this.onPRIVMSG.bind(this); this.irc.onMODE = this.onMODE.bind(this); this.irc.onERROR = this.onerror.bind(this); this.irc.onerror = this.onerror.bind(this); this.irc.onresponse = this.onresponse.bind(this); }, changeMyNick: function(nick){ this.nickname = nick; $$('.show-nick').set('html', nick); }, setNick: function(nickname){ if(!this.irc) return false; nickname = this.cleanNick(nickname); this.irc.connect(this.options.irc_server, this.options.irc_port); this.changeMyNick(nickname); return true; }, /** CHANNELS ACCESSORS */ getChannel: function(key){ return this.channels.get(this.toChan(key)); }, hasChannel: function(key){ return this.channels.has(this.toChan(key)); }, setChannel: function(key, chan){ this.channels.set(this.toChan(key), chan); }, eraseChannel: function(key){ this.channels.erase(this.toChan(key)); }, compareChannels: function(a, b){ return this.toChan(a) == this.toChan(b); }, /** * connect() is called two times, but only does something when irc is connected * AND nickname is set */ connect: function(){ if(this.nickname && this.connected){ this.irc.ident(this.nickname, '8 *', this.nickname); this.irc.nick(this.nickname); } }, onopen: function(){ this.connected = true; this.connect(); /* window.addEvent('unload', function(){ this.irc.reset(); }.bind(this)); */ }, onclose: function(){ }, onerror: function(cmd){ if(cmd.args[0] == "Closing Link: 127.0.0.1 (Connection Timed Out)"){ this.showInfo('Error occured, reconnecting...', 'error'); this.irc.connect(this.options.irc_server, this.options.irc_port); return; } var responseCode = parseInt(cmd.type); if (responseCode == 431 || responseCode == 432 || responseCode == 433) { // 431 ERR_NONICKNAMEGIVEN // 432 ERR_ERRONEUSNICKNAME // 433 ERR_NICKNAMEINUSE var nick = cmd.args[1]; if(responseCode == 432){ nick = this.cleanNick(nick); this.changeMyNick(nick); }else if(nick.length == 15){ } nick = (nick.length>= 15?nick.substr(1, 14):nick) + '_'; this.changeMyNick(nick); this.irc.nick(nick); //this.irc.ident(this.nickname, '8 *', this.nickname); }else if(responseCode == 451){ this.irc.ident(this.nickname, '8 *', this.nickname); }else{ this.showInfo(this.sanitize(cmd.args.pop()), 'error'); } }, onresponse: function(cmd){ var responseCode = parseInt(cmd.type); if(responseCode==372){ this.addMessage(this.options.systemChannel, this.options.systemUser, cmd.args[1].substr(2), 'info'); if(!this.joined){ var cnt = this.options.original_channels.length; for(var i = 0; i < cnt; i ++) { this.joinChannel(this.options.original_channels[i], i==0); } } }else if(responseCode==366){ this.addMessage(this.options.systemChannel, this.options.systemUser, 'Joined '+cmd.args[1]+' channel.', 'info user-join'); } else if(responseCode==353){ var channel = cmd.args[2]; var userList = cmd.args[3].split(/\s+/); userList.each(function(user){ if(user != '') this.addUser(channel, user, false, false); }.bind(this)); if(this.compareChannels(channel,this.currentChannel)){ var chan = this.getCurrentChannel(); chan.sortUsers(); this.buildUsers(); } } else if(responseCode==332){ this.addMessage(cmd.args[1], this.options.systemUser, ['Topic for '+cmd.args[1]+' is: ',new Element('i',{'text':this.sanitize(cmd.args[2])})],'info topic'); } else if(responseCode==333){ var d = new Date(); var time = parseInt(cmd.args[3], 10); d.setTime(time*1000); var when = d.toString(); this.addMessage(cmd.args[1], this.options.systemUser, ['Topic for '+cmd.args[1]+' was set by ',this.userLink(this.parseName(cmd.args[2])),' on '+when], 'info topic'); } }, /** * NAMES * CTCP VERSION */ onJOIN: function(cmd){ var chan = cmd.args[0]; var user = this.parseName(cmd.prefix); this.addUser(chan, user); }, onMODE: function(cmd){ var chan = this.getChannel(cmd.args[0]); if(chan){ var from = this.parseName(cmd.prefix); var user = cmd.args[2]; var op = cmd.args[1]; if(op.substr(0,1)=='+'){ var build = false; if(op.contains('o')){ this.addMessage(chan, this.options.systemUser, [this.userLink(from),' gives channel operator status to ',this.userLink(user)], 'info status'); chan.renameUser(user, '@'+user); build = true; }else if(op.contains('v')){ this.addMessage(chan, this.options.systemUser, [this.userLink(from),' gives channel operator status to ',this.userLink(user)], 'info status'); chan.renameUser(user, '+'+user); build = true; } if(build){ this.buildUsers(); } } } }, onPART: function(cmd){ var chan = this.getChannel(cmd.args[0]); if(chan){ var user = this.parseName(cmd.prefix); var msg = String(cmd.args[1]); chan.remUser(user); this.addMessage(chan.name, this.options.systemUser, user+' has left '+chan.name+(msg.length>0?' ('+msg+')':''), 'user-left'); if(chan.name==this.currentChannel){ this.remUser(user); } } }, onQUIT: function(cmd){ var user = this.parseName(cmd.prefix); this.channels.each(function(chan){ if(chan.remUser(user)){ this.addMessage(chan.name, this.options.systemUser, [this.userLink(user),' has quit ('+this.sanitize(cmd.args[0])+')'], 'info user-left'); if(chan.name==this.currentChannel){ this.remUser(user); } } }.bind(this)); }, onNICK: function(cmd){ this.changeNick(this.parseName(cmd.prefix), cmd.args[0]); }, onTOPIC: function(cmd){ var user = this.parseName(cmd.prefix); this.addMessage(cmd.args[0], this.options.systemUser, user+' has changed the topic to: '+cmd.args[1], 'info topic'); }, onCTCP: function(cmd){ if(cmd.args[1]=='VERSION'){ this.irc.ctcp(this.parseName(cmd.prefix), 'VERSION APE TCPSocket Demo (IRC) 2 - http://www.ape-project.com v'+this.version+' On a Web Browser (' + navigator.appCodeName + ')', true); }else{ } }, onACTION: function(cmd){ var user = this.parseName(cmd.prefix); var args = cmd.args; this.addMessage(args.shift(), this.options.systemUser, [this.userLink(user),' '+this.sanitize(args.join(' '))], 'info action'); }, onNOTICE: function(cmd){ var msg = cmd.args[1]; if(msg.charCodeAt(0)==1 && msg.charCodeAt(msg.length - 1) == 1){ // CTCP REPLY msg = msg.substr(1,msg.length-2).split(' '); this.addCTCP(cmd.args[0], msg.shift(), msg.join(' ')); }else{ if(cmd.prefix && cmd.prefix.substr(0,3)=='irc') this.addMessage(this.options.systemChannel, this.options.systemUser, this.sanitize(cmd.args[1]), 'info notice'); else this.showInfo(this.sanitize(cmd.args[1]), 'notice'); } }, onPRIVMSG: function(cmd){ var from = this.parseName(cmd.prefix); var chan = cmd.args[0]; var msg = cmd.args[1]; if(this.compareChannels(chan, this.nickname)){ this.joinUserChannel(from); this.addUser(from, from); chan = from; } this.addMessage(chan, from, this.sanitize(msg)); }, addCTCP: function(who, what, txt){ if(txt != undefined){ this.addMessage(this.currentChannel, '-'+who+'-', 'CTCP '+what+' REPLY: '+txt, 'ctcp'); }else{ this.addMessage(this.currentChannel, '>'+who+'<', 'CTCP '+what, 'ctcp') } }, getCurrentChannel: function(){ return this.getChannel(this.currentChannel); }, joinChannel: function(channel, switchTo){ if(!this.hasChannel(channel)){ var chan = new APE.IrcClient.Channel(channel, this.irc); this.setChannel(channel, chan); chan.join(); if(switchTo!==false) this.switchTo(channel); else this.buildTabs(); }else if(switchTo!==false){ this.switchTo(channel); } this.joined = true; }, switchTo: function(chan){ if(!this.hasChannel(chan)){ return; } var channel = this.getChannel(chan); /** For history */ channel.Hcnt = 0; this.currentChannel = chan; /* Updating messages and users*/ this.els.chatZone.getElements('div.msg_line').dispose(); this.buildTabs(); this.buildUsers(); this.checkLine(); this.buildMsg(); }, cleanNick: function(nick){ var ret = String(nick).replace(/[^a-zA-Z0-9\-_~]/g, ''); ret = ret.replace(/^[0-9]+/, ''); if(ret=='') return 'Guest'+Math.round(Math.random()*100); if(nick != ret) this.showError('Invalid nick "'+this.sanitize(nick)+'" changed to "'+ret+'".'); return ret; }, changeNick: function(from, to){ this.channels.each(function(chan){ if(chan.renameUser(from, to)){ this.addMessage(chan.name, this.options.systemUser, [this.userLink(from)," is now known as ", this.userLink(to)], 'info'); } if(this.compareChannels(chan.name, from)){ this.changeChannelName(from, to); } }.bind(this)); if(this.compareChannels(from, this.nickname)) this.changeMyNick(to); this.buildUsers(); }, changeChannelName: function(from, to){ var chan = this.getChannel(from); this.eraseChannel(from); chan.name = to; this.setChannel(to, chan); if(this.compareChannels(from, this.currentChannel)){ this.currentChannel = from; } this.buildTabs(); }, parseName: function(identity){ return identity.split("!", 1)[0]; }, remUser: function(user){ var go = new Fx.Morph($('user_line_'+user)); go.start({ 'height': 0 }); go.addEvent('complete', function(el){ el.destroy(); }) this.userCntAdd(-1); }, addUser: function(chan, user, showMessage, addNow){ var channel = this.getChannel(chan); if(!channel){ return; } var add; if(add = channel.addUser(user, addNow)){ if(this.compareChannels(chan, this.currentChannel) && addNow !== false){ if(channel.getLastUser().type != 'zzz' || add == 'rename'){ this.buildUsers(); }else{ this.writeUser(channel.buildLastUser( this.userClick.bindWithEvent(this) )); this.userCntAdd(1); } } if(showMessage !== false) this.addMessage(chan, this.options.systemUser, [this.userLink(user),' has joined'+(chan==user?'':' '+chan)], 'info user-join'); } }, writeUser: function(el_user){ this.els.userList.adopt(el_user); }, addMessage: function(chan, user, txt, special){ var channel = this.getChannel(chan); if(channel){ if(user != this.options.systemUser) user = this.userLink(user); channel.addMessage(user, txt, special, this.nickname); if(this.compareChannels(chan, this.currentChannel)){ this.writeMsg(channel.buildLastMessage()); } } }, writeMsg: function(el_line){ this.els.chatZone.adopt(el_line); this.scrollBottom(); this.checkLine(); }, checkLine: function(){ var chan = this.getCurrentChannel(); var lineW = chan.getLineWidth(); var textW = 590 - lineW; var line_size = Math.round((textW - 10) / 6); chan.line_size = line_size; // TODO Resize long lines $$('.msg_user').tween('width', lineW); $('line').tween('left', lineW); $$('.msg_text').tween('width', textW); }, scrollBottom: function(){ var scrollSize = this.els.chatZone.getScrollSize(); this.els.chatZone.scrollTo(0,scrollSize.y); }, sendMsg: function(msg, checkCmd){ var channel = this.getCurrentChannel(); channel.history.unshift(msg); channel.Hcnt = 0; //This is a command if(checkCmd !== false && msg.substr(0,1)=='/') this.sendCmd(msg.substr(1).split(' ')); else{ channel.send(msg); // the IRC server will not echo our message back, so simulate a send. this.onPRIVMSG({prefix:this.nickname,type:'PRIVMSG',args:[this.currentChannel, msg]}); } }, sendCmd: function(args){ switch(String(args[0]).toLowerCase()){ case 'j': case 'join': if(!args[1] || !args[1].match(/^(#|&)[^ ,]+$/)) this.showError('Invalid chan name "'+this.sanitize(args[1])+'" chan name must begin with # or & and must not contains any of \' \' (space) or \',\' (comma) and must contains at least 2 chars.') else this.joinChannel(args[1]) break; case 'clear': if(!args[1] || args[1]!='ALL'){ this.getCurrentChannel().clear(); }else{ this.channels.each(function(chan){ chan.clear(); }); } this.els.chatZone.empty(); break; case 'quit': args.shift(); window.location.reload(); this.irc.quit(args.join(' ')); break; case 'ctcp': args.shift(); var user = args.shift(); this.irc.ctcp(user, args.join(' ')); this.addCTCP(user, args[0]); break; case 'me': case 'action': if(!args[1]){ this.showError('Invalid arguments, /help for more informations'); } args.shift(); this.irc.action(this.currentChannel, args.join(' ')); this.onACTION({prefix:this.nickname,args:[this.currentChannel, args.join(' ')]}); break; case 'msg': case 'privmsg': if(args[1] == undefined || args[2] == undefined ){ this.showError('Invalid arguments, /help for more informations'); break; } args.shift(); var to = String(args.shift()); if(to.charAt(0)=='@') to = to.substr(1); var msg = args.join(' '); this.joinUserChannel(to); this.switchTo(to); this.sendMsg(msg, false); break; case 'nick': this.irc.nick(args[1]); //this.changeMyNick(args[1]); break; case 'h': case 'help': this.addMessage(this.currentChannel, this.options.helpUser,'\ List of available commands :\n\ \n\ /HELP , show this help.\n\ /H , alias for /HELP.\n\ /JOIN, joins the channel.\n\ /CLEAR [ALL], clear current channels, clears messages and input history.\n\ /QUIT [ ], disconnects from the server.\n\ /CTCP , send a CTCP message to nick, VERSION and USERINFO are commonly used.\n\ /ACTION , send a CTCP ACTION message, describing what you are doing.\n\ /PRIVMSG [@] , sends a private message.\n\ /MSG [@] , alias for /PRIVMSG.\n\ /NICK , sets you nickname.\n\ \n\ Commands are case insensitive, for example you can user /join or /JOIN.','help') break; default: this.showError('Unknow or unsuported command "'+this.sanitize(args[0])+'".'); } }, showInfo: function(msg, type){ this.addMessage(this.currentChannel, this.options.systemUser, msg, type); }, showError: function(msg){ this.showInfo(msg, 'error') }, sendKey: function(ev){ if(ev.key == 'enter') this.sendClick(); else if(ev.key == 'up'){ var chan = this.getCurrentChannel(); if(chan.Hcnt == 0){ chan.currMsg = this.els.input.value; } if(chan.history.length > chan.Hcnt){ var i = chan.Hcnt++; this.els.input.value = chan.history[i]; } }else if(ev.key == 'down'){ var chan = this.getCurrentChannel(); if(chan.Hcnt > 0){ var i = --chan.Hcnt; this.els.input.value = i==0?chan.currMsg:chan.history[i-1]; } }else if(ev.key=='tab'){ ev.stop(); var val = this.els.input.value; if(val.charAt(0)=='/' && !val.contains(' ')){ val = val.substr(1); var cmd_list = new Array( 'help', 'join', 'clear', 'quit', 'ctcp', 'action', 'privmsg', 'nick', 'msg' ); var check = function(cmd, index,array){ if(cmd.substr(0, val.length)==val){ return true; } return false; } var ok = cmd_list.filter(check); if(ok.length > 0){ this.els.input.value = '/'+ok[0].toUpperCase()+' '; } }else{ val = val.split(' '); var mot = val.pop(); if(mot.charAt(0)=='@') mot = mot.substr(1); if(mot.length > 0){ var chan = this.getCurrentChannel(); var usr = chan.search(mot); if(usr){ this.els.input.value = val.join(' ')+(val.length > 0?' ':'')+'@'+usr+' '; } } } } }, sendClick: function(){ var value = this.els.input.value; if(value.length > 0){ if(value.substr(0, 1)=='/' || this.currentChannel != this.options.systemChannel){ this.sendMsg(this.els.input.value); }else{ this.showError('No channel joined. Try /join # '); } } this.els.input.value = ''; }, tabClick: function(event, chan){ this.switchTo(chan); }, tabCloseClick: function(event, chan){ this.closeTab(chan); event.stop(); }, buildTabs: function(){ /* */ var current = this.currentChannel; this.els.tabs.empty(); var tabs = new Array(); this.channels.each(function(chan, key){ var el_tab = new Element('div', { 'class': 'tab'+(this.compareChannels(chan.name, current)?' current':'')+(this.compareChannels(key, this.options.systemChannel)?' sys':''), 'id': 'tab_'+key }); var el_link = new Element('span', { 'text': chan.name, 'class': 'link' }); var el_close = new Element('a', { 'href': '#' }); el_tab.grab(el_close); el_tab.grab(el_link); if(!this.compareChannels(chan.name, current)) el_link.addEvent('click', this.tabClick.bindWithEvent(this, key)); if(!this.compareChannels(chan.name, this.options.sysChannel)) el_close.addEvent('click', this.tabCloseClick.bindWithEvent(this, key)); tabs.push(el_tab); }.bind(this)); this.els.tabs.adopt(tabs); this.els.input.focus(); }, closeTab: function(tab){ if(this.compareChannels(tab,this.currentChannel)){ var keys = this.channels.getKeys(); var i = keys.indexOf(tab); var k = 0; if(i==keys.length-1){ k = i - 1; }else{ k = i+1; } this.switchTo(keys[k]); } var chan = this.getChannel(tab); chan.close(''); this.eraseChannel(tab); $('tab_'+tab).destroy(); }, buildUsers: function(){ this.els.userList.empty(); var users = this.getCurrentChannel().buildUsers(this.userClick.bindWithEvent(this)); this.writeUser(users); this.userCntSet(users.length); }, userCntSet: function(cnt){ this.els.userCnt.set('text', cnt); this.els.userCnt.store('cnt', cnt); }, userCntAdd: function(num){ var cnt = this.els.userCnt.retrieve('cnt', 0); this.userCntSet(cnt+num); }, userClick: function(ev, e2){ ev.stop(); var to = String(e2).replace(/^(@|\+)/g, ''); if(!this.hasChannel(to)){ this.joinVirtualChannel(to, this.irc, true, true); } this.switchTo(to); }, buildMsg: function(){ this.writeMsg(this.getCurrentChannel().buildMessages()); }, toChan: function(str){ return String(str).toLowerCase(); }, userLink: function(user){ var link = new Element('a', { 'text': user, 'class': 'user', 'href': '#' }); link.addEvent('click', this.userClick.bindWithEvent(this, user)); return link; }, sanitize: function(str){ //all text elements are grabed via appendText, so there is no need to escape return str; } /* sanitize: (function(str) { // See http://bigdingus.com/2007/12/29/html-escaping-in-javascript/ var MAP = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; var repl = function(c) { return MAP[c]; }; return function(s) { s = s.replace(/[&<>'"]/g, repl); return s; }; })() */ });#ape-project
APE.IrcClient.Channel = new Class({ name: false, dontclose:false, messages: new Array(), users: false, last_user: '', irc: null, line_size: 83, history: new Array(), MAX_HISTORY: 100, Hcnt: 0, initialize: function(name, irc, dontclose){ this.dontclose = dontclose===true; this.name = name; this.irc = irc; this.plop = rand_chars(); this.users = new Hash(); }, addUser: function(username, sort){ var type; switch(username.substr(0,1)){ case '@': type = 'operator'; break; case '+': type = 'voice'; break; default: type = 'zzz'; } if(type != 'zzz' && this.users.has(username.substr(1))){ this.renameUser(username.substr(1), username); this.users.get(username.type = type); return 'rename'; } if(this.users.has(String(username))){ return false; } this.users.set(username, { 'nick': username, 'type': type }); this.last_user = username; if(type != 'zzz' && sort !== false) this.sortUsers(); return true; }, sortUsers: function(){ var a_users = this.users.getValues(); a_users.sort(this.compareUsers); this.users.empty(); a_users.each(function(usr){ this.users.set(usr.nick, usr); }.bind(this)); }, compareUsers: function(a, b){ if(a.type == b.type){ return 0; }else if(a.type > b.type){ return 1; }else{ return -1 } }, remUser: function(username){ if(this.users.has(username)){ this.users.erase(username); return true; } return false; }, renameUser: function(from, to){ if(this.users.has(from)){ //var user = this.users.get(from); this.remUser(from); this.addUser(to); return true; }else if(this.users.has('@'+from)){ return this.renameUser('@'+from, (to.substr(0,1)=='@'||to.substr(0,1)=='+')?to:'@'+to); }else if(this.users.has('+'+from)){ return this.renameUser('+'+from, (to.substr(0,1)=='@'||to.substr(0,1)=='+')?to:'+'+to); } return false; }, getLastUser: function(){ return this.users.get(this.last_user); }, search: function(str){ var ret = false; this.users.each(function(usr){ var start = 0; if(usr.nick.charAt(0)=='@' || usr.nick.charAt(0)=='+') start = 1; if(usr.nick.substr(start, str.length) == str){ ret = usr.nick.substr(start); } }.bind(this)); return ret; }, userCount: function(){ return this.users.getLength(); }, addMessage: function(from, msg, special, user){ var txt = new Array(); msg = $type(msg)=='array'?msg:[msg]; msg.each(function(item){ if($type(item)!='element'){ txt = txt.concat(this.parseText(String(item))); }else{ txt.push(item); } }.bind(this)) var message = { 'name': from, 'msg' : txt, 'date': new Date(), 'special': special, 'usr': user }; this.messages.push(this.makeMessageLine(message)); if(this.messages.length > this.MAX_HISTORY) { var destroy = this.messages.slice(0, this.messages.length - this.MAX_HISTORY); this.messages = this.messages.slice(-this.MAX_HISTORY); destroy.each(function(el){ el.destroy(); }) } }, makeMessageLine: function(message){ /*me "plop" chan name must begin with # or & and must not contains any of ' ' (sp */ var el_txt = this.splitText(message); //Highlighting if(message.usr != undefined){ var reg = new RegExp('@?'+message.usr+'([^a-z~0-9\-_]|$)', 'i'); if(el_txt.get('text').match(reg)){ message.special = message.special==undefined?'highlight':message.special+' highlight'; } } var el_line = new Element('div', { 'class': 'msg_line'+(message.special?' '+message.special:'') }); var el_divu = new Element('div', { 'style': 'width:'+this.getLineWidth()+'px', 'class': 'msg_user' }); var hours = message.date.getHours(); hours = hours > 9 ? hours : '0'+hours; var min = message.date.getMinutes(); min = min > 9 ? min : '0'+min; var el_user = new Element('pre', { 'text': '['+hours+':'+min+'] '+($type(message.name)!="element"?message.name:'') }); if($type(message.name)=='element') el_user.grab(message.name); el_divu.grab(el_user); el_line.grab(el_divu); el_line.grab(el_txt); return el_line; }, join: function(){ if(this.irc) this.irc.join(this.name); }, splitWords: function(item){ var ret = new Array(); var tmp = item.split(' '); tmp.each(function(word, k, list){ ret.push( word + (k[12:30] UsernameTxt of the message]*>/g, '').length; } if(cur_size + length > this.line_size){ if(cur_size < this.line_size /2 && type!='element'){ while(word.length > this.line_size - cur_size){ ret.appendText(String(word).substr(0, this.line_size - cur_size -1)+'-'); word = String(word).substr(this.line_size - cur_size - 1); ret.appendText('\n'); cur_size = 0; } } ret.appendText('\n'); cur_size = 0; } if(type=='element'){ ret.grab(word); }else{ ret.appendText(String(word)); } cur_size += length; }.bind(this)); return ret; }, buildUsers: function(func){ var ret = new Array(); this.users.each(function(item, key){ ret.push(this.buildUser(key, func)); }.bind(this)); return ret; }, buildUser: function(usr, func){ /* user_name */ var user = this.users.get(usr); var el_usr = new Element('a', { 'class': 'user-item '+user.type, 'id': 'user_line_'+user.nick, 'text': user.nick, 'href': '#' }); el_usr.addEvent('click', function(ev, nick) { func(ev, user.nick)}.bindWithEvent(this, user.nick)); return el_usr; }, buildLastUser: function(func){ return this.buildUser(this.last_user, func); }, buildMessages: function(){ return this.messages; }, buildMessage: function(msg){ return this.messages[msg]; }, send: function(msg){ if(this.irc) this.irc.privmsg(this.name, msg); }, clear: function(){ this.messages = new Array(); this.history = new Array(); }, buildLastMessage: function(){ return this.buildMessage(this.messages.length -1); }, hasIrc: function(){ return this.irc?true:false; }, getMaxNicklength: function(){ // TODO Mise en cache du max et maj a l'ajout de messages var ret = 0; if(this.messages.length == 0) return 8; this.messages.each(function(msg){ var txt = msg.getElement('.msg_user').get('text'); ret = Math.max(txt.length-9, ret); }); return Math.max(ret, 8); }, getLineWidth: function(){ return (this.getMaxNicklength()+10)*6; }, parseText: function(txt){ txt = txt.replace('\03', ''); //Fo freenode special "cotes" txt = txt.replace(/\02/g, '"'); txt = txt.replace(/([a-z]{2,6}:\/\/[a-z0-9\-_#\/*+%.~,?&=]+)/gi, '\03$1\04$1\03'); txt = txt.replace(/([^a-z0-9\-_.\/]|^)((?:[a-z0-9\-_]\.?)*[a-z0-9\-_]\.(?:com|arpa|asia|pro|tel|travel|jobs|edu|gov|int|mill|net|org|biz|arpa|info|name|pro|aero|coop|museum|mobi|[a-z]{2})(?:\/[a-z0-9\-_#\/*+%.~,?&=]*)?)([^a-z]|$)/gi, '$1\03http://$2\04$2\03'); //Contains URLs if(txt.contains('\03')){ var tmp = new Array(); txt = txt.split('\03'); txt.each(function(item){ if(!item.contains('\04')){ tmp.push(item); }else{ var link = item.split('\04'); tmp.push(new Element('a', { 'href': link[0], 'text': link[1] })); } }); return tmp; } return [txt]; }, close: function(reason){ if(this.irc && !this.dontclose){ this.irc.part(this.name, reason); } } });
Ape.registerCmd("PROXY_CONNECT", true, function(params, infos) { if (!$defined(params.host) || !$defined(params.port)) { return 0; } var allowed = Ape.config('proxy.conf', 'allowed_hosts'); if (allowed != 'any') { var hosts = allowed.split(','); var isallowed = false; for (var i = 0; i < hosts.length; i++) { var parts = hosts[i].trim().split(':'); if (parts[0] == params.host) { if (parts.length == 2 && parts[1] != params.port) continue; isallowed = true; break; } } if (!isallowed) return [900, "NOT_ALLOWED"]; } var socket = new Ape.sockClient(params.port, params.host); socket.chl = infos.chl; /* TODO : Add socket to the user */ socket.onConnect = function() { /* "this" refer to socket object */ /* Create a new pipe (with a pubid) */ var pipe = new Ape.pipe(); infos.user.proxys.set(pipe.getProperty('pubid'), pipe); /* Set some private properties */ pipe.link = socket; pipe.nouser = false; this.pipe = pipe; /* Called when an user send a "SEND" command on this pipe */ pipe.onSend = function(user, params) { /* "this" refer to the pipe object */ this.link.write(Ape.base64.decode(unescape(params.msg))); } pipe.onDettach = function() { this.link.close(); } /* Send a PROXY_EVENT raw to the user and attach the pipe */ infos.user.pipe.sendRaw("PROXY_EVENT", {"event": "connect", "chl": this.chl}, {from: this.pipe}); } socket.onRead = function(data) { infos.user.pipe.sendRaw("PROXY_EVENT", {"event": "read", "data": Ape.base64.encode(data)}, {from: this.pipe}); } socket.onDisconnect = function(data) { if ($defined(this.pipe)) { if (!this.pipe.nouser) { /* User is not available anymore */ infos.user.pipe.sendRaw("PROXY_EVENT", {"event": "disconnect"}, {from: this.pipe}); infos.user.proxys.erase(this.pipe.getProperty('pubid')); } /* Destroy the pipe */ this.pipe.destroy(); } } return 1; }); Ape.addEvent("deluser", function(user) { user.proxys.each(function(val) { val.nouser = true; val.onDettach(); }); }); Ape.addEvent("adduser", function(user) { user.proxys = new $H; })
#ircchat{ width: 796px; height: 482px; margin-bottom: 10px; position:relative; margin: 0 auto; } /** Login **/ #ircchat #login{ width: 800px; height: 485px; position: absolute; top: 0; left: 0; background: white; z-index: 200; } #ircchat #login .popup{ border: solid 3px #235464; border-radius: 6px; -moz-border-radius:6px; -webkit-border-radius: 6px; padding: 40px 65px; margin: 150px 200px; background: #a4c9d6; text-align:center; } #ircchat #login .popup p{ font-weight: bold; color: #133d4b; font-size: 1.25em; margin-bottom: 20px; } #ircchat #login .popup input{ width: 125px; } #ircchat #login .popup button{ border: none; background: none transparent; color: #133d4b; cursor: pointer; } /** Connected **/ #ircchat .header{ height: 34px; } #ircchat .header .server{ float: left; margin: 9px 12px 0 1px; } #ircchat .header .tab{ width: auto; float:left; height: 29px; padding-right: 3px; background: transparent right top no-repeat url('./img/btnOff_right.png'); margin:0 7px 4px 0; } #ircchat .header .tab a{ display:block; height:29px; width: 29px; background: left top no-repeat url('./img/btnOff_left.png'); float: left; cursor: pointer; } #ircchat .header .tab span.link{ display: block; height: 20px; background: top repeat-x url('./img/btnOff_middle.png'); padding: 9px 9px 0 0; color: #9a9b9c; cursor: pointer; float:left; } #ircchat .header .tab a:hover{ background-position:bottom left; } #ircchat .header .tab.sys a{ width: 5px; cursor: default; } #ircchat .header .tab.sys span.link{ padding-left: 5px; } #ircchat .header .tab span.link:hover, #ircchat .header .tab.current span.link{ color: #235464; } #ircchat .header .tab.current{ background-image:url('./img/btnOn_right.png'); } #ircchat .header .tab.current a{ background-image:url('./img/btnOn_left.png'); cursor:pointer; } #ircchat .header .tab.sys.current a{ cursor: default; } #ircchat .header .tab.current span.link{ background-image:url('./img/btnOn_middle.png'); cursor: default; } #ircchat .chat{ width: 796px; height: 446px; border: solid 2px #839093; clear: left; } #ircchat .chat .texte-zone{ height: 398px; width: 620px; float: left; position: relative; z-index: 100; } #ircchat .chat .texte-zone .texte{ width: 620px; height: 398px; overflow-y: scroll; overflow-x: hidden; z-index: 101; font-family: "Lucida Console ", monospace !important; font-size: 10px; } #ircchat .chat .texte-zone #line{ width: 1px; height: 398px; position: absolute; top: 0; left: 100px; border-right: 1px solid #b5bcbc; z-index: 102; } /** Messages lines */ #ircchat .chat .texte-zone .texte .msg_line{ clear: both; } #ircchat .chat .texte-zone .texte .msg_line pre{ line-height: 13px !important; } #ircchat .chat .texte-zone .texte .msg_line .msg_user{ float:left; } #ircchat .chat .texte-zone .texte .msg_line .msg_text{ padding-left: 4px; overflow: hidden; float: left; /*For a cool effect float: right; /**/ } #ircchat .chat .list{ width: 175px; height: 398px; border-left: solid 1px #839095; float:left; } #ircchat .chat .list .header{ height: 20px; border-bottom: solid 1px #839095; background-color: #133d4b; text-align:center; color: #b9c5c9; padding-top: 5px; } #ircchat .chat .list .user-list{ overflow-y: scroll; height: 372px; background: top left url('./img/list_bg.png'); } #ircchat .chat .list .user-list #user-list{ overflow-y: auto; background: top left url('./img/list_bg.png'); } #ircchat .chat .list .user-list #user-list .user-item{ display: block; height: 15px; color: #FFF; padding: 5px 0 3px 27px; background: left top no-repeat; overflow: hidden; } #ircchat .chat .list .user-list #user-list a:hover{ background-color: #336474; } #ircchat .chat .list .user-list #user-list .user-item.operator{ background-image:url('./img/green_bullet.png'); } #ircchat .chat .list .user-list #user-list .user-item.voice{ background-image:url('./img/orange_bullet.png'); } #ircchat .chat .input-zone{ clear: left; height: 48px; background: repeat-x left top url('./img/send_bg.png'); } #ircchat .chat .input-zone .show-nick{ float:left; margin: 20px 0 0 8px; color: #5d5e5e; width: 105px; text-align: center; } #ircchat .chat .input-zone #chatButton{ float:right; border: none; background:none transparent; margin: 10px 20px 0 20px; padding: 0; height: 29px; color: #133d4b; font-size: 13px; cursor: pointer; } #ircchat .chat .input-zone #chat-input{ float:right; border: solid 1px #a1a1a1; border-top-color: #909090; border-bottom-color: #a9a9a9; margin-top: 10px; padding: 0; position: static; height: 19px; background: top repeat-x url('./img/input_bg.png') white; padding: 5px 4px; width: 601px; } /** Special Messages */ .highlight .msg_text{ color: red; } .info .msg_text{ color: gray; } .topic .msg_text{ color: #B037B0; } .user-left .msg_text{ color: #F44; } .user-join .msg_text{ color: #080 } .notice .msg_text{ font-style: italic; } .action .msg_text{ color: purple; } .ctcp .msg_text{ color: black; } .error .msg_text{ font-style: italic; color: #800; } /** Special spans */ .msg_text .special{ font-weight: bold; } .msg_text a.user{ } .msg_user a.user{ color: inherit; } .msg_user a.user:hover{ color: orange; }