import optparse, sys
import logging; log = logging.getLogger('pvl.irker')
# proto
import socket, json
def parser (parser, connect='tcp://localhost/', target=None) :
"""
Optparse option group.
"""
irker = optparse.OptionGroup(parser, 'Irker output')
irker.add_option('--irker-connect', metavar='URL', default=connect,
help="Irker daemon URL")
irker.add_option('--irker-target', metavar='IRC', default=target,
help="Irker target URL")
return irker
def apply (options, target=None) :
"""
Return Irker (XXX: target) from options.
"""
# XXX: None -> stdout
irk = Irker(options.irker_connect)
if options.irker_target :
target = options.irker_target
if target :
target = irk.target(target)
return irk, target
def connect (host=None, port=None, family=socket.AF_UNSPEC, socktype=socket.SOCK_STREAM) :
for af, st, proto, name, addr in socket.getaddrinfo(host, port, family, socktype) :
try :
s = socket.socket(af, st, proto)
except socket.error as error :
log.warning("%s:%s: socket: %s", host, port, error)
log.info("%s", name)
try :
s.connect(addr)
except socket.error as error :
log.warning("%s:%s: connect: %s", host, port, error)
return s
else :
raise Exception("Unable to connect: %s:%s" % (host, port))
class Irk (object) :
"""
Irker connection.
"""
PORT = 6659
@classmethod
def socket (cls, socket) :
return cls(socket.makefile('w'))
def __init__ (self, file) :
self.file = file
log.debug("%s", file)
def send (self, **opts) :
log.debug("%s", opts)
json.dump(opts, self.file)
self.file.write('\n')
self.file.flush()
def join (self, to) :
log.info("%s", to)
self.send(to=to, privmsg='')
def privmsg (self, to, *args) :
for arg in args :
log.info("%s: %s", to, arg)
self.send(to=to, privmsg=arg)
import urlparse
import functools
class Irker (object) :
"""
Reconnecting irker.
XXX: reconnect with state, or just crash and burn to be restarted?
"""
SCHEME = {
'tcp': (socket.AF_INET, socket.SOCK_STREAM),
'udp': (socket.AF_INET, socket.SOCK_DGRAM),
'unix': (socket.AF_UNIX, socket.SOCK_DGRAM),
}
def __init__ (self, url=None) :
if url :
self.url = urlparse.urlparse(url)
else :
self.url = None
self.targets = {}
self.irk = self.connect()
def connect (self) :
if not self.url :
# XXX: not here, do this in apply()?
return Irk(sys.stdout)
family, socktype = self.SCHEME[self.url.scheme]
if family == socket.AF_UNIX :
raise Exception("unix:// is not supported")
else :
# inet
s = connect(self.url.hostname, self.url.port or Irk.PORT, family=family, socktype=socktype)
irk = Irk.socket(s)
# rejoin
for target in self.targets :
irk.join(target)
return irk
def _target (self, target, *args) :
try :
for msg in args :
self.irk.send(to=target, privmsg=msg)
except IOError as ex :
log.warning("lost irk: %s", ex)
# XXX: reconnect?
self.irk = self.connect()
def target (self, target) :
"""
Bind to given target URL, returning a callable for sending messages.
"""
if target in self.targets :
_target = self.targets[target]
else :
self.irk.join(target)
_target = self.targets[target] = functools.partial(self._target, target)
return _target