PageRenderTime 35ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/core/irc.py

https://gitlab.com/foresterh/skybot
Python | 251 lines | 246 code | 5 blank | 0 comment | 3 complexity | 8fbdde9fd82d483b14924373fb176f72 MD5 | raw file
  1. import re
  2. import socket
  3. import time
  4. import thread
  5. import Queue
  6. from ssl import wrap_socket, CERT_NONE, CERT_REQUIRED, SSLError
  7. def decode(txt):
  8. for codec in ('utf-8', 'iso-8859-1', 'shift_jis', 'cp1252'):
  9. try:
  10. return txt.decode(codec)
  11. except UnicodeDecodeError:
  12. continue
  13. return txt.decode('utf-8', 'ignore')
  14. def censor(text):
  15. text = text.replace('\n', '').replace('\r', '')
  16. replacement = '[censored]'
  17. if 'censored_strings' in bot.config:
  18. words = map(re.escape, bot.config['censored_strings'])
  19. regex = re.compile('(%s)' % "|".join(words))
  20. text = regex.sub(replacement, text)
  21. return text
  22. class crlf_tcp(object):
  23. "Handles tcp connections that consist of utf-8 lines ending with crlf"
  24. def __init__(self, host, port, timeout=300):
  25. self.ibuffer = ""
  26. self.obuffer = ""
  27. self.oqueue = Queue.Queue() # lines to be sent out
  28. self.iqueue = Queue.Queue() # lines that were received
  29. self.socket = self.create_socket()
  30. self.host = host
  31. self.port = port
  32. self.timeout = timeout
  33. def create_socket(self):
  34. return socket.socket(socket.AF_INET, socket.TCP_NODELAY)
  35. def run(self):
  36. self.socket.connect((self.host, self.port))
  37. thread.start_new_thread(self.recv_loop, ())
  38. thread.start_new_thread(self.send_loop, ())
  39. def recv_from_socket(self, nbytes):
  40. return self.socket.recv(nbytes)
  41. def get_timeout_exception_type(self):
  42. return socket.timeout
  43. def handle_receive_exception(self, error, last_timestamp):
  44. if time.time() - last_timestamp > self.timeout:
  45. self.iqueue.put(StopIteration)
  46. self.socket.close()
  47. return True
  48. return False
  49. def recv_loop(self):
  50. last_timestamp = time.time()
  51. while True:
  52. try:
  53. data = self.recv_from_socket(4096)
  54. self.ibuffer += data
  55. if data:
  56. last_timestamp = time.time()
  57. else:
  58. if time.time() - last_timestamp > self.timeout:
  59. self.iqueue.put(StopIteration)
  60. self.socket.close()
  61. return
  62. time.sleep(1)
  63. except (self.get_timeout_exception_type(), socket.error) as e:
  64. if self.handle_receive_exception(e, last_timestamp):
  65. return
  66. continue
  67. while '\r\n' in self.ibuffer:
  68. line, self.ibuffer = self.ibuffer.split('\r\n', 1)
  69. self.iqueue.put(decode(line))
  70. def send_loop(self):
  71. while True:
  72. line = self.oqueue.get().splitlines()[0][:500]
  73. print ">>> %r" % line
  74. self.obuffer += line.encode('utf-8', 'replace') + '\r\n'
  75. while self.obuffer:
  76. sent = self.socket.send(self.obuffer)
  77. self.obuffer = self.obuffer[sent:]
  78. class crlf_ssl_tcp(crlf_tcp):
  79. "Handles ssl tcp connetions that consist of utf-8 lines ending with crlf"
  80. def __init__(self, host, port, ignore_cert_errors, timeout=300):
  81. self.ignore_cert_errors = ignore_cert_errors
  82. crlf_tcp.__init__(self, host, port, timeout)
  83. def create_socket(self):
  84. return wrap_socket(crlf_tcp.create_socket(self), server_side=False,
  85. cert_reqs=CERT_NONE if self.ignore_cert_errors else
  86. CERT_REQUIRED)
  87. def recv_from_socket(self, nbytes):
  88. return self.socket.read(nbytes)
  89. def get_timeout_exception_type(self):
  90. return SSLError
  91. def handle_receive_exception(self, error, last_timestamp):
  92. # this is terrible
  93. if not "timed out" in error.args[0]:
  94. raise
  95. return crlf_tcp.handle_receive_exception(self, error, last_timestamp)
  96. irc_prefix_rem = re.compile(r'(.*?) (.*?) (.*)').match
  97. irc_noprefix_rem = re.compile(r'()(.*?) (.*)').match
  98. irc_netmask_rem = re.compile(r':?([^!@]*)!?([^@]*)@?(.*)').match
  99. irc_param_ref = re.compile(r'(?:^|(?<= ))(:.*|[^ ]+)').findall
  100. class IRC(object):
  101. "handles the IRC protocol"
  102. #see the docs/ folder for more information on the protocol
  103. def __init__(self, server, nick, port=6667, channels=[], conf={}):
  104. self.channels = channels
  105. self.conf = conf
  106. self.server = server
  107. self.port = port
  108. self.nick = nick
  109. self.out = Queue.Queue() # responses from the server are placed here
  110. # format: [rawline, prefix, command, params,
  111. # nick, user, host, paramlist, msg]
  112. self.connect()
  113. thread.start_new_thread(self.parse_loop, ())
  114. def create_connection(self):
  115. return crlf_tcp(self.server, self.port)
  116. def connect(self):
  117. self.conn = self.create_connection()
  118. thread.start_new_thread(self.conn.run, ())
  119. self.set_pass(self.conf.get('server_password'))
  120. self.set_nick(self.nick)
  121. self.cmd("USER",
  122. [conf.get('user', 'skybot'), "3", "*", conf.get('realname',
  123. 'Python bot - http://github.com/rmmh/skybot')])
  124. def parse_loop(self):
  125. while True:
  126. msg = self.conn.iqueue.get()
  127. if msg == StopIteration:
  128. self.connect()
  129. continue
  130. if msg.startswith(":"): # has a prefix
  131. prefix, command, params = irc_prefix_rem(msg).groups()
  132. else:
  133. prefix, command, params = irc_noprefix_rem(msg).groups()
  134. nick, user, host = irc_netmask_rem(prefix).groups()
  135. paramlist = irc_param_ref(params)
  136. lastparam = ""
  137. if paramlist:
  138. if paramlist[-1].startswith(':'):
  139. paramlist[-1] = paramlist[-1][1:]
  140. lastparam = paramlist[-1]
  141. self.out.put([msg, prefix, command, params, nick, user, host,
  142. paramlist, lastparam])
  143. if command == "PING":
  144. self.cmd("PONG", paramlist)
  145. def set_pass(self, password):
  146. if password:
  147. self.cmd("PASS", [password])
  148. def set_nick(self, nick):
  149. self.cmd("NICK", [nick])
  150. def join(self, channel):
  151. self.cmd("JOIN", [channel])
  152. def msg(self, target, text):
  153. self.cmd("PRIVMSG", [target, text])
  154. def cmd(self, command, params=None):
  155. if params:
  156. params[-1] = ':' + params[-1]
  157. self.send(command + ' ' + ' '.join(map(censor, params)))
  158. else:
  159. self.send(command)
  160. def send(self, str):
  161. self.conn.oqueue.put(str)
  162. class FakeIRC(IRC):
  163. def __init__(self, server, nick, port=6667, channels=[], conf={}, fn=""):
  164. self.channels = channels
  165. self.conf = conf
  166. self.server = server
  167. self.port = port
  168. self.nick = nick
  169. self.out = Queue.Queue() # responses from the server are placed here
  170. self.f = open(fn, 'rb')
  171. thread.start_new_thread(self.parse_loop, ())
  172. def parse_loop(self):
  173. while True:
  174. msg = decode(self.f.readline()[9:])
  175. if msg == '':
  176. print "!!!!DONE READING FILE!!!!"
  177. return
  178. if msg.startswith(":"): # has a prefix
  179. prefix, command, params = irc_prefix_rem(msg).groups()
  180. else:
  181. prefix, command, params = irc_noprefix_rem(msg).groups()
  182. nick, user, host = irc_netmask_rem(prefix).groups()
  183. paramlist = irc_param_ref(params)
  184. lastparam = ""
  185. if paramlist:
  186. if paramlist[-1].startswith(':'):
  187. paramlist[-1] = paramlist[-1][1:]
  188. lastparam = paramlist[-1]
  189. self.out.put([msg, prefix, command, params, nick, user, host,
  190. paramlist, lastparam])
  191. if command == "PING":
  192. self.cmd("PONG", [params])
  193. def cmd(self, command, params=None):
  194. pass
  195. class SSLIRC(IRC):
  196. def __init__(self, server, nick, port=6667, channels=[], conf={},
  197. ignore_certificate_errors=True):
  198. self.ignore_cert_errors = ignore_certificate_errors
  199. IRC.__init__(self, server, nick, port, channels, conf)
  200. def create_connection(self):
  201. return crlf_ssl_tcp(self.server, self.port, self.ignore_cert_errors)