Merge branch 'master' of github.com:Araq/Nimrod

This commit is contained in:
Araq 2012-12-01 19:11:04 +01:00
commit 9c0355a13f
5 changed files with 108 additions and 88 deletions

View file

@ -35,6 +35,9 @@ import sockets, os
## that in the future this type's fields will not be exported therefore breaking ## that in the future this type's fields will not be exported therefore breaking
## your code. ## your code.
## ##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
##
## Asynchronous sockets ## Asynchronous sockets
## ==================== ## ====================
## ##

View file

@ -26,6 +26,10 @@ import sockets, strutils, parseutils, times, os, asyncio
## var ftp = FTPClient("example.org", user = "user", pass = "pass") ## var ftp = FTPClient("example.org", user = "user", pass = "pass")
## ftp.connect() ## ftp.connect()
## ftp.retrFile("file.ext", "file.ext") ## ftp.retrFile("file.ext", "file.ext")
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
type type
TFTPClient* = object of TObject TFTPClient* = object of TObject

View file

@ -22,6 +22,8 @@
## ##
## run(handleRequest, TPort(80)) ## run(handleRequest, TPort(80))
## ##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio

View file

@ -16,15 +16,21 @@
## the amount of lag. ## the amount of lag.
## ##
## .. code-block:: Nimrod ## .. code-block:: Nimrod
## var client = irc("irc.server.net", joinChans = @["#channel"]) ##
## var client = irc("picheta.me", joinChans = @["#bots"])
## client.connect() ## client.connect()
## while True: ## while True:
## var event: TIRCEvent ## var event: TIRCEvent
## if client.poll(event): ## if client.poll(event):
## case event.typ ## case event.typ
## of EvDisconnected: break ## of EvConnected: nil
## of EvDisconnected:
## client.reconnect()
## of EvMsg: ## of EvMsg:
## # Where all the magic happens. ## # Write your message reading code here.
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
import sockets, strutils, parseutils, times, asyncio import sockets, strutils, parseutils, times, asyncio
@ -34,11 +40,15 @@ type
port: TPort port: TPort
nick, user, realname, serverPass: string nick, user, realname, serverPass: string
case isAsync: bool case isAsync: bool
of false:
sock: TSocket
of true: of true:
handleEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent) {.closure.} handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.}
asyncSock: PAsyncSocket asyncSock: PAsyncSocket
myDispatcher: PDispatcher
of false:
dummyA: pointer
dummyB: pointer # workaround a Nimrod API issue
dummyC: pointer
sock: TSocket
status: TInfo status: TInfo
lastPing: float lastPing: float
lastPong: float lastPong: float
@ -47,6 +57,8 @@ type
msgLimit: bool msgLimit: bool
messageBuffer: seq[tuple[timeToSend: float, m: string]] messageBuffer: seq[tuple[timeToSend: float, m: string]]
PIRC* = ref TIRC
PAsyncIRC* = ref TAsyncIRC PAsyncIRC* = ref TAsyncIRC
TAsyncIRC* = object of TIRC TAsyncIRC* = object of TIRC
@ -68,9 +80,13 @@ type
MError MError
TIRCEventType* = enum TIRCEventType* = enum
EvMsg, EvDisconnected EvMsg, EvConnected, EvDisconnected
TIRCEvent* = object ## IRC Event TIRCEvent* = object ## IRC Event
case typ*: TIRCEventType case typ*: TIRCEventType
of EvConnected:
## Connected to server.
## Only occurs with AsyncIRC.
nil
of EvDisconnected: of EvDisconnected:
## Disconnected from the server ## Disconnected from the server
nil nil
@ -82,7 +98,7 @@ type
origin*: string ## The channel/user that this msg originated from origin*: string ## The channel/user that this msg originated from
raw*: string ## Raw IRC message raw*: string ## Raw IRC message
proc send*(irc: var TIRC, message: string, sendImmediately = false) = proc send*(irc: PIRC, message: string, sendImmediately = false) =
## Sends ``message`` as a raw command. It adds ``\c\L`` for you. ## Sends ``message`` as a raw command. It adds ``\c\L`` for you.
var sendMsg = true var sendMsg = true
if irc.msgLimit and not sendImmediately: if irc.msgLimit and not sendImmediately:
@ -104,15 +120,15 @@ proc send*(irc: var TIRC, message: string, sendImmediately = false) =
# but I can't exactly check for EBrokenPipe. # but I can't exactly check for EBrokenPipe.
irc.status = SockClosed irc.status = SockClosed
proc privmsg*(irc: var TIRC, target, message: string) = proc privmsg*(irc: PIRC, target, message: string) =
## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user. ## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user.
irc.send("PRIVMSG $1 :$2" % [target, message]) irc.send("PRIVMSG $1 :$2" % [target, message])
proc notice*(irc: var TIRC, target, message: string) = proc notice*(irc: PIRC, target, message: string) =
## Sends ``notice`` to ``target``. ``Target`` can be a channel, or a user. ## Sends ``notice`` to ``target``. ``Target`` can be a channel, or a user.
irc.send("NOTICE $1 :$2" % [target, message]) irc.send("NOTICE $1 :$2" % [target, message])
proc join*(irc: var TIRC, channel: string, key = "") = proc join*(irc: PIRC, channel: string, key = "") =
## Joins ``channel``. ## Joins ``channel``.
## ##
## If key is not ``""``, then channel is assumed to be key protected and this ## If key is not ``""``, then channel is assumed to be key protected and this
@ -122,16 +138,19 @@ proc join*(irc: var TIRC, channel: string, key = "") =
else: else:
irc.send("JOIN " & channel & " " & key) irc.send("JOIN " & channel & " " & key)
proc part*(irc: var TIRC, channel, message: string) = proc part*(irc: PIRC, channel, message: string) =
## Leaves ``channel`` with ``message``. ## Leaves ``channel`` with ``message``.
irc.send("PART " & channel & " :" & message) irc.send("PART " & channel & " :" & message)
proc close*(irc: var TIRC) = proc close*(irc: PIRC) =
## Closes connection to an IRC server. ## Closes connection to an IRC server.
## ##
## **Warning:** This procedure does not send a ``QUIT`` message to the server. ## **Warning:** This procedure does not send a ``QUIT`` message to the server.
irc.status = SockClosed irc.status = SockClosed
irc.sock.close() if irc.isAsync:
irc.asyncSock.close()
else:
irc.sock.close()
proc isNumber(s: string): bool = proc isNumber(s: string): bool =
## Checks if `s` contains only numbers. ## Checks if `s` contains only numbers.
@ -202,12 +221,11 @@ proc parseMessage(msg: string): TIRCEvent =
inc(i) # Skip `:`. inc(i) # Skip `:`.
result.params.add(msg[i..msg.len-1]) result.params.add(msg[i..msg.len-1])
proc connect*(irc: var TIRC) = proc connect*(irc: PIRC) =
## Connects to an IRC server as specified by ``irc``. ## Connects to an IRC server as specified by ``irc``.
assert(irc.address != "") assert(irc.address != "")
assert(irc.port != TPort(0)) assert(irc.port != TPort(0))
irc.sock = socket()
irc.sock.connect(irc.address, irc.port) irc.sock.connect(irc.address, irc.port)
irc.status = SockConnected irc.status = SockConnected
@ -217,13 +235,21 @@ proc connect*(irc: var TIRC) =
irc.send("NICK " & irc.nick, true) irc.send("NICK " & irc.nick, true)
irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true)
proc reconnect*(irc: PIRC) =
## Reconnects to an IRC server.
##
## This should be used when an ``EvDisconnected`` event occurs.
irc.sock = socket()
irc.connect()
proc irc*(address: string, port: TPort = 6667.TPort, proc irc*(address: string, port: TPort = 6667.TPort,
nick = "NimrodBot", nick = "NimrodBot",
user = "NimrodBot", user = "NimrodBot",
realname = "NimrodBot", serverPass = "", realname = "NimrodBot", serverPass = "",
joinChans: seq[string] = @[], joinChans: seq[string] = @[],
msgLimit: bool = true): TIRC = msgLimit: bool = true): PIRC =
## Creates a ``TIRC`` object. ## Creates a ``TIRC`` object.
new(result)
result.address = address result.address = address
result.port = port result.port = port
result.nick = nick result.nick = nick
@ -237,8 +263,9 @@ proc irc*(address: string, port: TPort = 6667.TPort,
result.msgLimit = msgLimit result.msgLimit = msgLimit
result.messageBuffer = @[] result.messageBuffer = @[]
result.status = SockIdle result.status = SockIdle
result.sock = socket()
proc processLine(irc: var TIRC, line: string): TIRCEvent = proc processLine(irc: PIRC, line: string): TIRCEvent =
if line.len == 0: if line.len == 0:
irc.close() irc.close()
result.typ = EvDisconnected result.typ = EvDisconnected
@ -271,7 +298,7 @@ proc processLine(irc: var TIRC, line: string): TIRCEvent =
if result.nick == irc.nick: if result.nick == irc.nick:
irc.nick = result.params[0] irc.nick = result.params[0]
proc processOther(irc: var TIRC, ev: var TIRCEvent): bool = proc processOther(irc: PIRC, ev: var TIRCEvent): bool =
result = false result = false
if epochTime() - irc.lastPing >= 20.0: if epochTime() - irc.lastPing >= 20.0:
irc.lastPing = epochTime() irc.lastPing = epochTime()
@ -290,7 +317,7 @@ proc processOther(irc: var TIRC, ev: var TIRCEvent): bool =
break # messageBuffer is guaranteed to be from the quickest to the break # messageBuffer is guaranteed to be from the quickest to the
# later-est. # later-est.
proc poll*(irc: var TIRC, ev: var TIRCEvent, proc poll*(irc: PIRC, ev: var TIRCEvent,
timeout: int = 500): bool = timeout: int = 500): bool =
## This function parses a single message from the IRC server and returns ## This function parses a single message from the IRC server and returns
## a TIRCEvent. ## a TIRCEvent.
@ -316,46 +343,32 @@ proc poll*(irc: var TIRC, ev: var TIRCEvent,
if processOther(irc, ev): result = true if processOther(irc, ev): result = true
proc getLag*(irc: var TIRC): float = proc getLag*(irc: PIRC): float =
## Returns the latency between this client and the IRC server in seconds. ## Returns the latency between this client and the IRC server in seconds.
## ##
## If latency is unknown, returns -1.0. ## If latency is unknown, returns -1.0.
return irc.lag return irc.lag
proc isConnected*(irc: var TIRC): bool = proc isConnected*(irc: PIRC): bool =
## Returns whether this IRC client is connected to an IRC server. ## Returns whether this IRC client is connected to an IRC server.
return irc.status == SockConnected return irc.status == SockConnected
proc getNick*(irc: var TIRC): string = proc getNick*(irc: PIRC): string =
## Returns the current nickname of the client. ## Returns the current nickname of the client.
return irc.nick return irc.nick
# -- Asyncio dispatcher # -- 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.asyncSock = AsyncSocket()
irc.asyncSock.connect(irc.address, irc.port)
proc handleConnect(s: PAsyncSocket, irc: PAsyncIRC) = proc handleConnect(s: PAsyncSocket, irc: PAsyncIRC) =
# Greet the server :) # Greet the server :)
if irc.serverPass != "": irc[].send("PASS " & irc.serverPass, true) if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true)
irc[].send("NICK " & irc.nick, true) irc.send("NICK " & irc.nick, true)
irc[].send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true)
discard """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 irc.status = SockConnected
"""
var ev: TIRCEvent
ev.typ = EvConnected
irc.handleEvent(irc, ev)
proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) = proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) =
var line = "".TaintedString var line = "".TaintedString
@ -363,42 +376,48 @@ proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) =
if ret: if ret:
if line == "": if line == "":
var ev: TIRCEvent var ev: TIRCEvent
irc[].close() irc.close()
ev.typ = EvDisconnected ev.typ = EvDisconnected
irc[].handleEvent(irc[], ev) irc.handleEvent(irc, ev)
else: else:
var ev = irc[].processLine(line.string) var ev = irc.processLine(line.string)
irc[].handleEvent(irc[], ev) irc.handleEvent(irc, ev)
discard """proc handleRead(h: PObject) =
var irc = PAsyncIRC(h)
var line = "".TaintedString
var ret = irc.sock.recvLineAsync(line)
case ret
of RecvFullLine:
var ev = irc[].processLine(irc.lineBuffer.string & line.string)
irc.handleEvent(irc[], ev, irc.userArg)
irc.lineBuffer = "".TaintedString
of RecvPartialLine:
if line.string != "":
string(irc.lineBuffer).add(line.string)
of RecvDisconnected:
var ev: TIRCEvent
irc[].close()
ev.typ = EvDisconnected
irc.handleEvent(irc[], ev, irc.userArg)
of RecvFail: nil"""
proc handleTask(s: PAsyncSocket, irc: PAsyncIRC) = proc handleTask(s: PAsyncSocket, irc: PAsyncIRC) =
var ev: TIRCEvent var ev: TIRCEvent
if irc[].processOther(ev): if irc.processOther(ev):
irc.handleEvent(irc[], ev) irc.handleEvent(irc, ev)
proc register*(d: PDispatcher, irc: PAsyncIRC) =
## Registers ``irc`` with dispatcher ``d``.
irc.asyncSock.handleConnect =
proc (s: PAsyncSocket) =
handleConnect(s, irc)
irc.asyncSock.handleRead =
proc (s: PAsyncSocket) =
handleRead(s, irc)
irc.asyncSock.handleTask =
proc (s: PAsyncSocket) =
handleTask(s, irc)
d.register(irc.asyncSock)
irc.myDispatcher = d
proc connect*(irc: PAsyncIRC) =
## Equivalent of connect for ``TIRC`` but specifically created for asyncio.
assert(irc.address != "")
assert(irc.port != TPort(0))
discard """proc handleTask(h: PObject) = irc.asyncSock.connect(irc.address, irc.port)
var irc = PAsyncIRC(h)
var ev: TIRCEvent proc reconnect*(irc: PAsyncIRC) =
if PAsyncIRC(h)[].processOther(ev): ## Reconnects to an IRC server.
irc.handleEvent(irc[], ev, irc.userArg)""" ##
## This should be used when an ``EvDisconnected`` event occurs.
##
## When successfully reconnected an ``EvConnected`` event will occur.
irc.asyncSock = AsyncSocket()
irc.myDispatcher.register(irc)
irc.connect()
proc asyncIRC*(address: string, port: TPort = 6667.TPort, proc asyncIRC*(address: string, port: TPort = 6667.TPort,
nick = "NimrodBot", nick = "NimrodBot",
@ -406,7 +425,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort,
realname = "NimrodBot", serverPass = "", realname = "NimrodBot", serverPass = "",
joinChans: seq[string] = @[], joinChans: seq[string] = @[],
msgLimit: bool = true, msgLimit: bool = true,
ircEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent) {.closure.} ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.}
): PAsyncIRC = ): PAsyncIRC =
## Use this function if you want to use asyncio's dispatcher. ## Use this function if you want to use asyncio's dispatcher.
## ##
@ -429,19 +448,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort,
result.msgLimit = msgLimit result.msgLimit = msgLimit
result.messageBuffer = @[] result.messageBuffer = @[]
result.handleEvent = ircEvent result.handleEvent = ircEvent
result.asyncSock = AsyncSocket()
proc register*(d: PDispatcher, irc: PAsyncIRC) =
## Registers ``irc`` with dispatcher ``d``.
irc.asyncSock.handleConnect =
proc (s: PAsyncSocket) =
handleConnect(s, irc)
irc.asyncSock.handleRead =
proc (s: PAsyncSocket) =
handleRead(s, irc)
irc.asyncSock.handleTask =
proc (s: PAsyncSocket) =
handleTask(s, irc)
d.register(irc.asyncSock)
when isMainModule: when isMainModule:
#var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)") #var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)")
@ -456,6 +463,8 @@ when isMainModule:
var event: TIRCEvent var event: TIRCEvent
if client.poll(event): if client.poll(event):
case event.typ case event.typ
of EvConnected:
nil
of EvDisconnected: of EvDisconnected:
break break
of EvMsg: of EvMsg:

View file

@ -23,6 +23,8 @@
## ##
## run(handleRequest) ## run(handleRequest)
## ##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
import sockets, strutils, os, strtabs, asyncio import sockets, strutils, os, strtabs, asyncio