CommandParser splitting needs refactoring really...
if not self.schedule:\r
self.schedule = reactor.callLater(self.throttle - t, self.flush, True)\r
return\r
+ else:\r
+ # process the rest of the packet\r
+ if not scheduled:\r
+ if not self.schedule:\r
+ self.schedule = reactor.callLater(0, self.flush, True)\r
+ return\r
+ \r
self.throttle = t + config.UPDATE_FREQ\r
\r
encdata = simplejson.dumps(self.buffer)\r
nick, ident, ip, realname = f["nick"], f["ident"], f["ip"], f["realname"]\r
\r
hmac = hmacfn(ident, ip)\r
- self.write("NICK %s" % nick)\r
self.write("USER %s bleh bleh %s %s :%s" % (ident, ip, hmac, realname))\r
+ self.write("NICK %s" % nick)\r
\r
self.factory.client = self\r
self("connect")\r
-Numerics = {"001": "RPL_WELCOME", "433": "ERR_NICKNAMEINUSE", "004": "RPL_MYINFO", "005": "RPL_ISUPPORT", "353": "RPL_NAMREPLY", "366": "RPL_ENDOFNAMES", "331": "RPL_NOTOPIC", "332": "RPL_TOPIC", "333": "RPL_TOPICWHOTIME"}\r
+Numerics = {"001": "RPL_WELCOME", "433": "ERR_NICKNAMEINUSE", "004": "RPL_MYINFO", "005": "RPL_ISUPPORT", "353": "RPL_NAMREPLY", "366": "RPL_ENDOFNAMES", "331": "RPL_NOTOPIC", "332": "RPL_TOPIC", "333": "RPL_TOPICWHOTIME"};\r
+\r
+registeredCTCPs = {\r
+ "VERSION": function(x) { return "qwebirc 0.01. Copyright (C) Chris Porter 2008"; },\r
+ "USERINFO": function(x) { return "qwebirc"; },\r
+ "TIME": function(x) { function pad(x) { x = "" + x; if(x.length == 1) x = "0" + x; return x; }var d = new Date(); return DaysOfWeek[d.getDay()] + " " + MonthsOfYear[d.getMonth()] + " " + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + " " + d.getFullYear() },\r
+ "PING": function(x) { return x; },\r
+ "CLIENTINFO": function(x) { return "PING VERSION TIME USERINFO CLIENTINFO"; },\r
+};\r
\r
function BaseIRCClient(nickname, view) {\r
var self = this;\r
this.signedOn = false;\r
this.pmodes = ["b", "k,", "o", "l", "v"];\r
this.channels = {}\r
+ var nextctcp = 0;\r
\r
/* attempt javascript inheritence! */\r
this.dispatch = function(data) {\r
\r
this.irc_NICK = function(prefix, params) {\r
var user = prefix;\r
- var oldnick = user.split("!", 1);\r
+ var oldnick = hosttonick(user);\r
var newnick = params[0];\r
\r
if(self.nickname == oldnick)\r
return true;\r
}\r
\r
+ var processCTCP = function(message) {\r
+ if(message.charAt(0) == "\x01") {\r
+ if(message.charAt(message.length - 1) == "\x01") {\r
+ message = message.substr(1, message.length - 2);\r
+ } else {\r
+ message = message.substr(1);\r
+ }\r
+ return message.splitMax(" ", 2);\r
+ }\r
+ }\r
+ \r
this.irc_PRIVMSG = function(prefix, params) {\r
var user = prefix;\r
var target = params[0];\r
var message = ANI(params, -1);\r
\r
- if(target == self.nickname) {\r
- view.userPrivmsg(user, message);\r
+ var ctcp = processCTCP(message);\r
+ if(ctcp) {\r
+ var type = ctcp[0].toUpperCase();\r
+ \r
+ var replyfn = registeredCTCPs[type];\r
+ if(replyfn) {\r
+ var t = new Date().getTime() / 1000;\r
+ if(t > nextctcp)\r
+ self.send("NOTICE " + hosttonick(user) + " :\x01" + type + " " + replyfn(ctcp[1]) + "\x01");\r
+ nextctcp = t + 5;\r
+ }\r
+ \r
+ if(target == self.nickname) {\r
+ view.userCTCP(user, type, ctcp[1]);\r
+ } else {\r
+ view.channelCTCP(user, target, type, ctcp[1]);\r
+ }\r
} else {\r
- view.channelPrivmsg(user, target, message);\r
+ if(target == self.nickname) {\r
+ view.userPrivmsg(user, message);\r
+ } else {\r
+ view.channelPrivmsg(user, target, message);\r
+ }\r
}\r
\r
return true;\r
if(user == "") {\r
view.serverNotice(message);\r
} else if(target == self.nickname) {\r
- view.userNotice(user, message);\r
+ var ctcp = processCTCP(message);\r
+ if(ctcp) {\r
+ view.userCTCPReply(user, ctcp[0], ctcp[1]);\r
+ } else {\r
+ view.userNotice(user, message);\r
+ }\r
} else {\r
view.channelNotice(user, target, message);\r
}\r
var supportedhash = {};\r
\r
for(var i=0;i<supported.length;i++) {\r
- var l = supported[i].split("=", 2);\r
+ var l = supported[i].splitMax("=", 2);\r
view.supported(l[0], l[1]);\r
}\r
}\r
--- /dev/null
+function CommandParser(ui, send) {\r
+ var self = this;\r
+ var aliases = {\r
+ "J": "JOIN",\r
+ "K": "KICK",\r
+ "MSG": "PRIVMSG",\r
+ "Q": "QUERY"\r
+ };\r
+ \r
+ this.dispatch = function(line) {\r
+ if(line.length == 0)\r
+ return;\r
+\r
+ if(line.charAt(0) != "/") {\r
+ self.cmd_SAY(line);\r
+ return;\r
+ }\r
+ \r
+ var line = line.substr(1);\r
+ var allargs = line.splitMax(" ", 2);\r
+ var command = allargs[0].toUpperCase();\r
+ var args = allargs[1];\r
+ \r
+ var aliascmd = aliases[command];\r
+ if(aliascmd)\r
+ command = aliascmd;\r
+ \r
+ var commando = self["cmd_" + command];\r
+ if(!commando) {\r
+ if(args) {\r
+ send(command + " " + args);\r
+ } else {\r
+ send(command);\r
+ }\r
+ } else {\r
+ commando(args);\r
+ }\r
+ }\r
+ \r
+ this.cmd_KICK = function(args) {\r
+ var allargs = args.splitMax(" ", 3);\r
+ \r
+ if(allargs.length < 2) {\r
+ ui.errorMessage("Insufficient arguments for command.")\r
+ return;\r
+ }\r
+\r
+ var channel = allargs[0];\r
+ var target = allargs[1];\r
+ var message = allargs[2];\r
+ if(!message)\r
+ message = "";\r
+ \r
+ send("KICK " + channel + " " + target + " :" + message);\r
+ }\r
+\r
+ var newTargetLine = function(target, type, message, extra) {\r
+ if(!extra)\r
+ extra = {}\r
+ \r
+ extra["n"] = ui.getNickname();\r
+ extra["m"] = message;\r
+ extra["t"] = target;\r
+ \r
+ var window = ui.getWindow(target);\r
+ var channel;\r
+ if(!window) {\r
+ type = "TARGETED" + type;\r
+ target = false;\r
+ } else if(window.ischannel) {\r
+ type = "CHAN" + type;\r
+ } else {\r
+ type = "PRIV" + type;\r
+ }\r
+ ui.newLine(target, "OUR" + type, extra);\r
+ }\r
+ \r
+ this.cmd_ME = function(args) {\r
+ var w = ui.getActiveWindow();\r
+ if(!w || w.name == "") {\r
+ ui.errorMessage("Can't use this command in this window");\r
+ return;\r
+ }\r
+ if(args == undefined)\r
+ args = "";\r
+ \r
+ send("PRIVMSG " + w.name + " :\x01ACTION " + args + "\x01");\r
+ newTargetLine(w.name, "ACTION", args);\r
+ }\r
+ \r
+ this.cmd_CTCP = function(args) {\r
+ var allargs = args.splitMax(" ", 3);\r
+ if(allargs.length < 2) {\r
+ ui.errorMessage("Insufficient arguments for command.");\r
+ return;\r
+ }\r
+ \r
+ var target = allargs[0];\r
+ var type = allargs[1].toUpperCase();\r
+ var message = allargs[2];\r
+ if(message == undefined)\r
+ message = "";\r
+\r
+ if(message == "") {\r
+ send("PRIVMSG " + target + " :\x01" + type + "\x01");\r
+ } else {\r
+ send("PRIVMSG " + target + " :\x01" + type + " " + message + "\x01");\r
+ }\r
+ \r
+ newTargetLine(target, "CTCP", message, {"x": type});\r
+ }\r
+ \r
+ this.cmd_PRIVMSG = function(args) {\r
+ var allargs = args.splitMax(" ", 2);\r
+ \r
+ if(allargs.length < 2) {\r
+ ui.errorMessage("Insufficient arguments for command.")\r
+ return;\r
+ }\r
+\r
+ var target = allargs[0];\r
+ var message = allargs[1];\r
+ \r
+ newTargetLine(target, "MSG", message);\r
+ send("PRIVMSG " + target + " :" + message);\r
+ }\r
+ \r
+ this.cmd_NOTICE = function(args) {\r
+ var allargs = args.splitMax(" ", 2);\r
+ \r
+ if(allargs.length < 2) {\r
+ ui.errorMessage("Insufficient arguments for command.")\r
+ return;\r
+ }\r
+ \r
+ var target = allargs[0];\r
+ var message = allargs[1];\r
+\r
+ newTargetLine(target, "NOTICE", message);\r
+ send("NOTICE " + target + " :" + message);\r
+ }\r
+ \r
+ this.cmd_QUERY = function(args) {\r
+ var allargs = args.splitMax(" ", 2);\r
+\r
+ if(allargs.length < 1) {\r
+ ui.errorMessage("Insufficient arguments for command.")\r
+ return;\r
+ }\r
+ \r
+ ui.newWindow(allargs[0]);\r
+ ui.selectTab(allargs[0]);\r
+ \r
+ if((allargs.length > 1) && (allargs[1] != ""))\r
+ self.cmd_PRIVMSG(args);\r
+ }\r
+ \r
+ this.cmd_SAY = function(args) {\r
+ var w = ui.getActiveWindow();\r
+ if(!w || w.name == "") {\r
+ ui.errorMessage("Can't use this command in this window");\r
+ return;\r
+ }\r
+ \r
+ self.cmd_PRIVMSG(w.name + " " + args);\r
+ }\r
+}\r
this.prefixes = "@+";\r
this.modeprefixes = "ov";\r
\r
- newLine = function(window, type, data) {\r
+ var newLine = function(window, type, data) {\r
if(!data)\r
data = {};\r
\r
ui.newLine(window, type, data);\r
}\r
\r
- newChanLine = function(channel, type, user, extra) {\r
+ var newChanLine = function(channel, type, user, extra) {\r
if(!extra)\r
extra = {};\r
\r
extra["n"] = hosttonick(user);\r
extra["h"] = hosttohost(user);\r
extra["c"] = channel;\r
+ extra["-"] = self.nickname;\r
\r
newLine(channel, type, extra);\r
}\r
\r
- newServerLine = function(type, data) {\r
+ var newServerLine = function(type, data) {\r
newLine("", type, data);\r
}\r
+\r
+ var newActiveLine = function(type, data) {\r
+ newLine(false, type, data);\r
+ }\r
\r
this.rawNumeric = function(numeric, prefix, params) {\r
newServerLine("RAW", {"n": "numeric", "m": params.slice(1).join(" ")});\r
var clist = [];\r
for(var c in channels) {\r
clist.push(c);\r
- newChanLine(c, "QUIT", user);\r
+ newChanLine(c, "QUIT", user, {"m": message});\r
}\r
\r
self.tracker.removeNick(nick);\r
}\r
\r
this.channelTopic = function(user, channel, topic) {\r
- newChanLine(channel, "TOPIC", user, {"m": topic}); \r
+ newChanLine(channel, "TOPIC", user, {"m": topic});\r
ui.updateTopic(channel, topic);\r
}\r
\r
ui.updateTopic(channel, topic);\r
}\r
\r
+ this.chanCTCP = function(user, channel, type, args) {\r
+ if(args == undefined)\r
+ args = "";\r
+\r
+ if(type == "ACTION") {\r
+ newChanLine(channel, "CHANACTION", user, {"m": args, "c": channel});\r
+ return;\r
+ }\r
+ \r
+ newChanLine(channel, "CHANCTCP", user, {"x": type, "m": args, "c": channel});\r
+ }\r
+ \r
+ this.userCTCP = function(user, type, args) {\r
+ var nick = hosttonick(user);\r
+ var host = hosttohost(user);\r
+ if(args == undefined)\r
+ args = "";\r
+ \r
+ if(type == "ACTION") { \r
+ ui.newWindow(nick, false);\r
+ newLine(nick, "PRIVACTION", {"m": args, "x": type, "h": host, "n": nick});\r
+ return;\r
+ }\r
+ \r
+ if(ui.getWindow(nick)) {\r
+ newLine(nick, "PRIVCTCP", {"m": args, "x": type, "h": host, "n": nick, "-": self.nickname});\r
+ } else {\r
+ newActiveLine("PRIVCTCP", {"m": args, "x": type, "h": host, "n": nick, "-": self.nickname});\r
+ }\r
+ }\r
+ \r
+ this.userCTCPReply = function(user, type, args) {\r
+ var nick = hosttonick(user);\r
+ var host = hosttohost(user);\r
+ if(args == undefined)\r
+ args = "";\r
+ \r
+ if(ui.getWindow(nick)) {\r
+ newLine(nick, "CTCPREPLY", {"m": args, "x": type, "h": host, "n": nick, "-": self.nickname});\r
+ } else {\r
+ newActiveLine("CTCPREPLY", {"m": args, "x": type, "h": host, "n": nick, "-": self.nickname});\r
+ }\r
+ }\r
+ \r
this.channelPrivmsg = function(user, channel, message) {\r
newChanLine(channel, "CHANMSG", user, {"m": message});\r
}\r
this.userPrivmsg = function(user, message) {\r
var nick = hosttonick(user);\r
var host = hosttohost(user);\r
-\r
ui.newWindow(nick, false);\r
newLine(nick, "PRIVMSG", {"m": message, "h": host, "n": nick});\r
}\r
var nick = hosttonick(user);\r
var host = hosttohost(user);\r
\r
- newServerLine("NOTICE", {"m": message, "h": host, "n": nick});\r
+ if(ui.getWindow(nick)) {\r
+ newLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick});\r
+ } else {\r
+ newActiveLine("PRIVNOTICE", {"m": message, "h": host, "n": nick});\r
+ }\r
}\r
\r
this.userInvite = function(user, channel) {\r
}\r
\r
this.disconnected = function() {\r
+ for(var x in this.parent.channels) {\r
+ ui.closeWindow(x);\r
+ }\r
+\r
self.tracker = undefined;\r
\r
newServerLine("DISCONNECT");\r
}\r
\r
this.parent = new BaseIRCClient(nickname, this);\r
- ui.send = this.parent.send;\r
+ this.commandparser = new CommandParser(ui, this.parent.send);\r
+ ui.send = this.commandparser.dispatch;\r
+ ui.getNickname = function() {\r
+ return self.nickname;\r
+ }\r
+ \r
this.connect = this.parent.connect;\r
this.disconnect = this.parent.disconnect;\r
}\r
}\r
return c;\r
}\r
+\r
+/* how horribly inefficient (again) */\r
+String.prototype.splitMax = function(by, max) {\r
+ var items = this.split(by);\r
+ var newitems = items.slice(0, max-1);\r
+\r
+ if(items.length >= max)\r
+ newitems.push(items.slice(max-1).join(by));\r
+ \r
+ return newitems;\r
+}\r
+\r
+alert("a".splitMax(" ", 2));\r
+alert("a b".splitMax(" ", 2));\r
+alert("a b c".splitMax(" ", 2));\r
+alert("a b".splitMax(" ", 3));\r
+alert("a b c".splitMax(" ", 3));\r
+alert("a".splitMax(" ", 4));\r
+alert("a b".splitMax(" ", 4));\r
+alert("a b c".splitMax(" ", 4));\r
+alert("a".splitMax(" ", 1));\r
+alert("a b".splitMax(" ", 1));\r
+alert("a b c".splitMax(" ", 1));\r
+\r
+DaysOfWeek = {\r
+ 0: "Sun",\r
+ 1: "Mon",\r
+ 2: "Tue",\r
+ 3: "Wed",\r
+ 4: "Thu",\r
+ 5: "Fri",\r
+ 6: "Sat"\r
+}\r
+\r
+MonthsOfYear = {\r
+ 0: "Jan",\r
+ 1: "Feb",\r
+ 2: "Mar",\r
+ 3: "Apr",\r
+ 4: "May",\r
+ 5: "Jun",\r
+ 6: "Jul",\r
+ 7: "Aug",\r
+ 8: "Sep",\r
+ 9: "Oct",\r
+ 10: "Nov",\r
+ 11: "Dec"\r
+}\r
+\r
"CHANNOTICE": [\r
"-$n:$c- $m"\r
],\r
- "NOTICE": [\r
+ "PRIVNOTICE": [\r
"-$n- $m"\r
+ ],\r
+ "OURCHANMSG": [\r
+ "<$n> $m"\r
+ ],\r
+ "OURPRIVMSG": [\r
+ "<$n> $m"\r
+ ],\r
+ "OURTARGETEDMSG": [\r
+ "*$t* $m"\r
+ ],\r
+ "OURTARGETEDNOTICE": [\r
+ "[notice($t)] $m"\r
+ ],\r
+ "OURCHANNOTICE": [\r
+ "-$n:$t- $m"\r
+ ],\r
+ "OURPRIVNOTICE": [\r
+ "-$n- $m"\r
+ ],\r
+ "OURCHANACTION": [\r
+ " * $n $m"\r
+ ],\r
+ "OURPRIVACTION": [\r
+ " * $n $m"\r
+ ],\r
+ "CHANACTION": [\r
+ " * $n $m"\r
+ ],\r
+ "PRIVACTION": [\r
+ " * $n $m"\r
+ ],\r
+ "CHANCTCP": [\r
+ "$n [$h] requested CTCP $x from $c: $m"\r
+ ],\r
+ "PRIVCTCP": [\r
+ "$n [$h] requested CTCP $x from $-: $m"\r
+ ],\r
+ "CTCPREPLY": [\r
+ "CTCP $x reply from $n: $m"\r
+ ],\r
+ "OURCHANCTCP": [\r
+ "[ctcp($t)] $x $m"\r
+ ],\r
+ "OURPRIVCTCP": [\r
+ "[ctcp($t)] $x $m"\r
+ ],\r
+ "OURTARGETEDCTCP": [\r
+ "[ctcp($t)] $x $m"\r
]\r
}\r
var self = this;\r
var active;\r
\r
- var tabs = new Element("div", {"styles": { "border": "1px solid black", "padding": "4px" } });\r
+ var tabs = new Element("div", {"styles": { "border": "1px solid black", "padding": "4px", "font-family": "Lucida Console" } });\r
parent.appendChild(tabs);\r
var tabhash = {};\r
\r
tabs.appendChild(tab);\r
\r
if(windowname != "") {\r
- var tabclose = new Element("span", {"styles": { "border": "1px black solid" } });\r
+ var tabclose = new Element("span", {"styles": { "border": "1px black solid", "margin-left": "5px", "padding": "2px", "font-size": "0.5em" } });\r
tabclose.addEvent("click", function() {\r
if(ischannel)\r
- self.send("PART " + windowname);\r
+ self.send("/PART " + windowname);\r
+\r
self.closeWindow(windowname);\r
});\r
tabclose.setText("X");\r
tab.appendChild(tabclose);\r
}\r
- tabhash[windowname] = { "container": container, "tab": tab, "element": e, "lastcolour": false, "nicklist": nicklist, "topic": topic };\r
+ tabhash[windowname] = { "name": windowname, "container": container, "tab": tab, "element": e, "lastcolour": false, "nicklist": nicklist, "topic": topic, "ischannel": ischannel };\r
\r
return tabhash[windowname];\r
}\r
\r
+ this.getWindow = function(windowname) {\r
+ return tabhash[windowname];\r
+ }\r
+ \r
this.updateNickList = function(windowname, nicks) {\r
var w = tabhash[windowname];\r
if(!w)\r
}\r
\r
this.selectTab = function(windowname) {\r
+ var w = tabhash[windowname];\r
+ if(!w)\r
+ return;\r
+ \r
for(var i in tabhash) {\r
var o = tabhash[i];\r
o.container.setStyle("display", "none");\r
o.tab.setStyle("background", "#eee");\r
}\r
\r
- tabhash[windowname].container.setStyle("display", "block");\r
- tabhash[windowname].tab.setStyle("background", "#dff");\r
- tabhash[windowname].tab.setStyle("color", "");\r
+ w.container.setStyle("display", "block");\r
+ w.tab.setStyle("background", "#dff");\r
+ w.tab.setStyle("color", "");\r
self.active = windowname;\r
}\r
\r
- this.newLine = function(windowname, type, line, colour) {\r
+ this.newLine = function(windowname, type, line, colour) {\r
var window = tabhash[windowname];\r
if(!window) {\r
- window = tabhash[""];\r
- windowname = "";\r
+ if(windowname == false) {\r
+ windowname = self.active;\r
+ window = tabhash[self.active];\r
+ } else {\r
+ window = tabhash[""];\r
+ windowname = "";\r
+ }\r
}\r
\r
var wx = window;\r
wx.tab.setStyle("color", "red");\r
}\r
\r
+ this.getActiveWindow = function() {\r
+ return tabhash[self.active];\r
+ }\r
+ \r
this.closeWindow = function(windowname) {\r
var w = tabhash[windowname];\r
if(!w)\r
\r
window.removeChild(w.container);\r
tabs.removeChild(w.tab);\r
- self.selectTab("");\r
+ \r
+ if(self.active == windowname)\r
+ self.selectTab("");\r
\r
delete tabhash[windowname];\r
}\r
+ \r
+ this.errorMessage = function(message) {\r
+ self.newLine(false, "", message, "red");\r
+ }\r
}\r
<script type="text/javascript" src="js/irc/irclib.js"></script>\r
<script type="text/javascript" src="js/irc/baseirc.js"></script>\r
<script type="text/javascript" src="js/irc/irctracker.js"></script>\r
+ <script type="text/javascript" src="js/irc/commandparser.js"></script>\r
<script type="text/javascript" src="js/irc/ircclient.js"></script>\r
<script type="text/javascript"> \r
var ui;\r
ui = new UglyUI($("ircui"), theme);\r
\r
ui.newWindow("", false, "Status");\r
- ui.newLine("", "", "Welcome to QuakeNet webIRC", "#f99");\r
ui.selectTab("");\r
+ ui.newLine("", "", "Welcome to QuakeNet webIRC -- comments/bugs to slug@quakenet.org.", "#f99");\r
\r
- var IRC = new IRCClient(prompt("Nickname","moofishaax"), ui);\r
+ var IRC = new IRCClient(prompt("Nickname","uberfish"), ui);\r
IRC.connect();\r
});\r
\r