PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/idiokit/irc.py

https://bitbucket.org/clarifiednetworks/idiokit/
Python | 157 lines | 154 code | 3 blank | 0 comment | 0 complexity | 09690f422ace3a0ae27b29ffbc5889bf MD5 | raw file
  1. import re
  2. import util
  3. import threado
  4. import sockets
  5. class IRCError(Exception):
  6. pass
  7. class IRCParser(object):
  8. def __init__(self):
  9. self.line_buffer = util.LineBuffer()
  10. def feed(self, data=""):
  11. if "\x00" in data:
  12. raise IRCError("NUL not allowed in messages")
  13. for line in self.line_buffer.feed(data):
  14. if len(line) > 510:
  15. raise IRCError("too long message (over 512 bytes)")
  16. yield self.process_line(line)
  17. def process_line(self, line):
  18. if line.startswith(":"):
  19. bites = line.split(" ", 2)
  20. prefix = bites.pop(0)[1:]
  21. else:
  22. bites = line.split(" ", 1)
  23. prefix = None
  24. if len(bites) != 2:
  25. raise IRCError("invalid message")
  26. command, trailing = bites
  27. params = list()
  28. while trailing:
  29. if trailing.startswith(":"):
  30. params.append(trailing[1:])
  31. break
  32. bites = trailing.split(" ", 1)
  33. params.append(bites.pop(0))
  34. trailing = "".join(bites)
  35. return prefix, command, params
  36. def format_message(command, *params):
  37. message = [command] + list(params[:-1]) + [":" + "".join(params[-1:])]
  38. return " ".join(message) + "\r\n"
  39. ERROR_REX = re.compile("^(4|5)\d\d$")
  40. class NickAlreadyInUse(IRCError):
  41. pass
  42. def mutations(*nicks):
  43. for nick in nicks:
  44. yield nick
  45. for suffix in ["_", "-", "~", "^"]:
  46. for nick in nicks:
  47. yield nick[:9-len(suffix)] + suffix
  48. for nick in nicks:
  49. for i in range(1000000):
  50. suffix = str(i)
  51. yield nick[:9-len(suffix)] + suffix
  52. class IRC(threado.GeneratorStream):
  53. def __init__(self, server, port,
  54. ssl=False, ssl_verify_cert=True, ssl_ca_certs=None):
  55. threado.GeneratorStream.__init__(self)
  56. self.server = server
  57. self.port = port
  58. self.ssl = ssl
  59. self.ssl_verify_cert = ssl_verify_cert
  60. self.ssl_ca_certs = ssl_ca_certs
  61. self.parser = None
  62. self.socket = None
  63. @threado.stream
  64. def connect(inner, self, nick, password=None):
  65. self.parser = IRCParser()
  66. self.socket = sockets.Socket()
  67. yield inner.sub(self.socket.connect((self.server, self.port)))
  68. if self.ssl:
  69. yield inner.sub(self.socket.ssl(verify_cert=self.ssl_verify_cert,
  70. ca_certs=self.ssl_ca_certs))
  71. nicks = mutations(nick)
  72. if password is not None:
  73. self.socket.send(format_message("PASS", password))
  74. self.socket.send(format_message("NICK", nicks.next()))
  75. self.socket.send(format_message("USER", nick, nick, "-", nick))
  76. while True:
  77. source, data = yield threado.any(inner, self.socket)
  78. if inner is source:
  79. continue
  80. for prefix, command, params in self.parser.feed(data):
  81. if command == "PING":
  82. self.socket.send(format_message("PONG", *params))
  83. continue
  84. if command == "001":
  85. self.start()
  86. inner.finish(nick)
  87. if command == "433":
  88. for nick in nicks:
  89. self.socket.send(format_message("NICK", nick))
  90. break
  91. else:
  92. raise NickAlreadyInUse("".join(params[-1:]))
  93. continue
  94. if ERROR_REX.match(command):
  95. raise IRCError("".join(params[-1:]))
  96. def nick(self, nick):
  97. self.send("NICK", nick)
  98. def quit(self, message=None):
  99. if message is None:
  100. self.send("QUIT")
  101. else:
  102. self.send("QUIT", message)
  103. def join(self, channel, key=None):
  104. if key is None:
  105. self.send("JOIN", channel)
  106. else:
  107. self.send("JOIN", channel, key)
  108. def run(self):
  109. for prefix, command, params in self.parser.feed():
  110. if command == "PING":
  111. self.send("PONG", *params)
  112. self.inner.send(prefix, command, params)
  113. while True:
  114. source, data = yield threado.any(self.inner, self.socket)
  115. if self.inner is source:
  116. self.socket.send(format_message(*data))
  117. continue
  118. for prefix, command, params in self.parser.feed(data):
  119. if command == "PING":
  120. self.send("PONG", *params)
  121. self.inner.send(prefix, command, params)