diff options
Diffstat (limited to 'lib/pure/irc.nim')
-rw-r--r-- | lib/pure/irc.nim | 188 |
1 files changed, 143 insertions, 45 deletions
diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim index 3e1fa1c2c..09e85f234 100644 --- a/lib/pure/irc.nim +++ b/lib/pure/irc.nim @@ -26,15 +26,15 @@ ## of EvMsg: ## # Where all the magic happens. -import sockets, strutils, parseutils, times +import sockets, strutils, parseutils, times, asyncio type - TIRC* = object + TIRC* = object of TObject address: string port: TPort nick, user, realname, serverPass: string sock: TSocket - connected: bool + status: TInfo lastPing: float lastPong: float lag: float @@ -42,6 +42,11 @@ type msgLimit: bool messageBuffer: seq[tuple[timeToSend: float, m: string]] + PAsyncIRC* = ref TAsyncIRC + TAsyncIRC* = object of TIRC + userArg: PObject + handleEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent, userArg: PObject) + TIRCMType* = enum MUnknown, MNumeric, @@ -89,7 +94,7 @@ proc send*(irc: var TIRC, message: string, sendImmediately = false) = except EOS: # Assuming disconnection of every EOS could be bad, # but I can't exactly check for EBrokenPipe. - irc.connected = false + irc.status = SockClosed proc privmsg*(irc: var TIRC, target, message: string) = ## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user. @@ -188,7 +193,7 @@ proc connect*(irc: var TIRC) = irc.sock = socket() irc.sock.connect(irc.address, irc.port) - irc.connected = true + irc.status = SockConnected # Greet the server :) if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true) @@ -201,7 +206,7 @@ proc irc*(address: string, port: TPort = 6667.TPort, realname = "NimrodBot", serverPass = "", joinChans: seq[string] = @[], msgLimit: bool = true): TIRC = - ## This function calls `connect`, so you don't need to. + ## Creates a ``TIRC`` object. result.address = address result.port = port result.nick = nick @@ -214,45 +219,33 @@ proc irc*(address: string, port: TPort = 6667.TPort, result.channelsToJoin = joinChans result.msgLimit = msgLimit result.messageBuffer = @[] + result.status = SockIdle - result.connect() - -proc poll*(irc: var TIRC, ev: var TIRCEvent, - timeout: int = 500): bool = - ## This function parses a single message from the IRC server and returns - ## a TIRCEvent. - ## - ## This function should be called often as it also handles pinging - ## the server. - if not irc.connected: ev.typ = EvDisconnected - var line = TaintedString"" - var socks = @[irc.sock] - var ret = socks.select(timeout) - if socks.len() == 0 and ret == 1: - if irc.sock.recvLine(line): - if line.string.len == 0: - ev.typ = EvDisconnected - else: - ev = parseMessage(line.string) - # Get the origin - ev.origin = ev.params[0] - if ev.origin == irc.nick: ev.origin = ev.nick - - if ev.cmd == MError: - ev.typ = EvDisconnected - return - - if ev.cmd == MPing: - irc.send("PONG " & ev.params[0]) - if ev.cmd == MPong: - irc.lag = epochTime() - parseFloat(ev.params[ev.params.high]) - irc.lastPong = epochTime() - if ev.cmd == MNumeric: - if ev.numeric == "001": - for chan in items(irc.channelsToJoin): - irc.join(chan) - result = true +proc processLine(irc: var TIRC, line: string): TIRCEvent = + if line.len == 0: + result.typ = EvDisconnected + else: + result = parseMessage(line) + # Get the origin + result.origin = result.params[0] + if result.origin == irc.nick: result.origin = result.nick + + if result.cmd == MError: + result.typ = EvDisconnected + return + if result.cmd == MPing: + irc.send("PONG " & result.params[0]) + if result.cmd == MPong: + irc.lag = epochTime() - parseFloat(result.params[result.params.high]) + irc.lastPong = epochTime() + if result.cmd == MNumeric: + if result.numeric == "001": + for chan in items(irc.channelsToJoin): + irc.join(chan) + +proc processOther(irc: var TIRC, ev: var TIRCEvent): bool = + result = false if epochTime() - irc.lastPing >= 20.0: irc.lastPing = epochTime() irc.send("PING :" & formatFloat(irc.lastPing), true) @@ -260,7 +253,6 @@ proc poll*(irc: var TIRC, ev: var TIRCEvent, if epochTime() - irc.lastPong >= 120.0 and irc.lastPong != -1.0: ev.typ = EvDisconnected # TODO: EvTimeout? return true - for i in 0..irc.messageBuffer.len-1: if epochTime() >= irc.messageBuffer[0][0]: @@ -270,6 +262,30 @@ proc poll*(irc: var TIRC, ev: var TIRCEvent, break # messageBuffer is guaranteed to be from the quickest to the # later-est. +proc poll*(irc: var TIRC, ev: var TIRCEvent, + timeout: int = 500): bool = + ## This function parses a single message from the IRC server and returns + ## a TIRCEvent. + ## + ## This function should be called often as it also handles pinging + ## the server. + ## + ## This function provides a somewhat asynchronous IRC implementation, although + ## it should only be used for simple things for example an IRC bot which does + ## not need to be running many time critical tasks in the background. If you + ## require this, use the asyncio implementation. + + if not (irc.status == SockConnected): ev.typ = EvDisconnected + var line = TaintedString"" + var socks = @[irc.sock] + var ret = socks.select(timeout) + if socks.len() == 0 and ret != 0: + if irc.sock.recvLine(line): + ev = irc.processLine(line) + result = true + + if processOther(irc, ev): result = true + proc getLag*(irc: var TIRC): float = ## Returns the latency between this client and the IRC server in seconds. ## @@ -278,8 +294,88 @@ proc getLag*(irc: var TIRC): float = proc isConnected*(irc: var TIRC): bool = ## Returns whether this IRC client is connected to an IRC server. - return irc.connected + return irc.status == SockConnected + +# -- Asyncio dispatcher + +proc connect*(irc: PAsyncIRC) = + ## Equivalent of connect for ``TIRC`` but specifically created for asyncio. + assert(irc.address != "") + assert(irc.port != TPort(0)) + + irc.sock = socket() + irc.sock.setBlocking(false) + irc.sock.connectAsync(irc.address, irc.port) + irc.status = SockConnecting + +proc handleConnect(h: PObject) = + var irc = PAsyncIRC(h) + + # Greet the server :) + if irc.serverPass != "": irc[].send("PASS " & irc.serverPass, true) + irc[].send("NICK " & irc.nick, true) + irc[].send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) + irc.status = SockConnected + +proc handleRead(h: PObject) = + var irc = PAsyncIRC(h) + var line = "" + if irc.sock.recvLine(line): + var ev = irc[].processLine(line) + irc.handleEvent(irc[], ev, irc.userArg) + +proc handleTask(h: PObject) = + var irc = PAsyncIRC(h) + var ev: TIRCEvent + if PAsyncIRC(h)[].processOther(ev): + irc.handleEvent(irc[], ev, irc.userArg) + +proc asyncIRC*(address: string, port: TPort = 6667.TPort, + nick = "NimrodBot", + user = "NimrodBot", + realname = "NimrodBot", serverPass = "", + joinChans: seq[string] = @[], + msgLimit: bool = true, + ircEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent, + userArg: PObject), + userArg: PObject = nil): PAsyncIRC = + ## Use this function if you want to use asyncio's dispatcher. + ## + ## **Note:** Do **NOT** use this if you're writing a simple IRC bot which only + ## requires one task to be run, i.e. this should not be used if you want a + ## synchronous IRC client implementation, use ``irc`` for that. + + new(result) + result.address = address + result.port = port + result.nick = nick + result.user = user + result.realname = realname + result.serverPass = serverPass + result.lastPing = epochTime() + result.lastPong = -1.0 + result.lag = -1.0 + result.channelsToJoin = joinChans + result.msgLimit = msgLimit + result.messageBuffer = @[] + result.handleEvent = ircEvent + result.userArg = userArg + +proc register*(d: PDispatcher, irc: PAsyncIRC) = + ## Registers ``irc`` with dispatcher ``d``. + var dele = newDelegate() + dele.deleVal = irc + dele.getSocket = (proc (h: PObject): tuple[info: TInfo, sock: TSocket] = + if PAsyncIRC(h).status == SockConnecting or + PAsyncIRC(h).status == SockConnected: + return (PAsyncIRC(h).status, PAsyncIRC(h).sock) + else: return (SockIdle, PAsyncIRC(h).sock)) + dele.handleConnect = handleConnect + dele.handleRead = handleRead + dele.task = handleTask + d.register(dele) + when isMainModule: #var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)") #echo(repr(m)) @@ -288,6 +384,7 @@ when isMainModule: var client = irc("amber.tenthbit.net", nick="TestBot1234", joinChans = @["#flood"]) + client.connect() while True: var event: TIRCEvent if client.poll(event): @@ -305,3 +402,4 @@ when isMainModule: #echo( repr(event) ) #echo("Lag: ", formatFloat(client.getLag())) #""" + |