PageRenderTime 215ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/jsb/drivers/irc/bot.py

https://code.google.com/p/boty/
Python | 413 lines | 377 code | 17 blank | 19 comment | 14 complexity | e3e54eb5a945ebd559c269112cb06bf2 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, MIT
  1. # jsb/socklib/irc/bot.py
  2. #
  3. #
  4. #
  5. """
  6. a bot object handles the dispatching of commands and check for callbacks
  7. that need to be fired.
  8. """
  9. ## jsb imports
  10. from jsb.utils.exception import handle_exception
  11. from jsb.utils.generic import waitforqueue, uniqlist, strippedtxt
  12. from jsb.lib.commands import cmnds
  13. from jsb.lib.callbacks import callbacks
  14. from jsb.lib.plugins import plugs as plugins
  15. from jsb.lib.threads import start_new_thread, threaded
  16. from jsb.utils.dol import Dol
  17. from jsb.utils.pdod import Pdod
  18. from jsb.lib.persiststate import PersistState
  19. from jsb.lib.errors import NoSuchCommand
  20. from jsb.lib.channelbase import ChannelBase
  21. from jsb.lib.exit import globalshutdown
  22. from jsb.lib.botbase import BotBase
  23. from jsb.lib.eventbase import EventBase
  24. from jsb.lib.partyline import partyline
  25. from jsb.lib.wait import waiter
  26. from channels import Channels
  27. from irc import Irc
  28. from ircevent import IrcEvent
  29. ## basic imports
  30. from collections import deque
  31. import re
  32. import socket
  33. import struct
  34. import Queue
  35. import time
  36. import os
  37. import types
  38. import logging
  39. ## defines
  40. dccchatre = re.compile('\001DCC CHAT CHAT (\S+) (\d+)\001', re.I)
  41. ## classes
  42. class IRCBot(Irc):
  43. """ class that dispatches commands and checks for callbacks to fire. """
  44. def __init__(self, cfg={}, users=None, plugs=None, *args, **kwargs):
  45. Irc.__init__(self, cfg, users, plugs, *args, **kwargs)
  46. if self.state:
  47. if not self.state.has_key('opchan'): self.state['opchan'] = []
  48. if not self.state.has_key('joinedchannels'): self.state['joinedchannels'] = []
  49. def _resume(self, data, botname, reto=None):
  50. """ resume the bot. """
  51. if not Irc._resume(self, data, botname, reto): return 0
  52. for channel in self.state['joinedchannels']: self.who(channel)
  53. return 1
  54. def _dccresume(self, sock, nick, userhost, channel=None):
  55. """ resume dcc loop. """
  56. if not nick or not userhost: return
  57. start_new_thread(self._dccloop, (sock, nick, userhost, channel))
  58. def _dcclisten(self, nick, userhost, channel):
  59. """ accept dcc chat requests. """
  60. try:
  61. listenip = socket.gethostbyname(socket.gethostname())
  62. (port, listensock) = getlistensocket(listenip)
  63. ipip2 = socket.inet_aton(listenip)
  64. ipip = struct.unpack('>L', ipip2)[0]
  65. chatmsg = 'DCC CHAT CHAT %s %s' % (ipip, port)
  66. self.ctcp(nick, chatmsg)
  67. self.sock = sock = listensock.accept()[0]
  68. except Exception, ex:
  69. handle_exception()
  70. logging.error('%s - dcc error: %s' % (self.cfg.name, str(ex)))
  71. return
  72. self._dodcc(sock, nick, userhost, channel)
  73. def _dodcc(self, sock, nick, userhost, channel=None):
  74. """ send welcome message and loop for dcc commands. """
  75. if not nick or not userhost: return
  76. try:
  77. sock.send('Welcome to the JSONBOT partyline ' + nick + " ;]\n")
  78. partylist = partyline.list_nicks()
  79. if partylist: sock.send("people on the partyline: %s\n" % ' .. '.join(partylist))
  80. sock.send("control character is ! .. bot broadcast is @\n")
  81. except Exception, ex:
  82. handle_exception()
  83. logging.error('%s - dcc error: %s' % (self.cfg.name, str(ex)))
  84. return
  85. start_new_thread(self._dccloop, (sock, nick, userhost, channel))
  86. def _dccloop(self, sock, nick, userhost, channel=None):
  87. """ loop for dcc commands. """
  88. sockfile = sock.makefile('r')
  89. sock.setblocking(True)
  90. res = ""
  91. partyline.add_party(self, sock, nick, userhost, channel)
  92. while 1:
  93. time.sleep(0.01)
  94. try:
  95. res = sockfile.readline()
  96. logging.warn("%s - dcc - %s got %s" % (self.cfg.name, userhost, res))
  97. if self.stopped or not res:
  98. logging.warn('%s - closing dcc with %s' % (self.cfg.name, nick))
  99. partyline.del_party(nick)
  100. return
  101. except socket.timeout:
  102. continue
  103. except socket.error, ex:
  104. try:
  105. (errno, errstr) = ex
  106. except:
  107. errno = 0
  108. errstr = str(ex)
  109. if errno == 35 or errno == 11:
  110. continue
  111. else:
  112. raise
  113. except Exception, ex:
  114. handle_exception()
  115. logging.warn('%s - closing dcc with %s' % (self.cfg.name, nick))
  116. partyline.del_party(nick)
  117. return
  118. try:
  119. res = self.normalize(res)
  120. ievent = IrcEvent()
  121. ievent.printto = sock
  122. ievent.bottype = "irc"
  123. ievent.nick = nick
  124. ievent.userhost = userhost
  125. ievent.auth = userhost
  126. ievent.channel = channel or ievent.userhost
  127. ievent.origtxt = res
  128. ievent.txt = res
  129. ievent.cmnd = 'DCC'
  130. ievent.cbtype = 'DCC'
  131. ievent.bot = self
  132. ievent.sock = sock
  133. ievent.speed = 1
  134. ievent.isdcc = True
  135. ievent.msg = True
  136. logging.debug("%s - dcc - constructed event" % self.cfg.name)
  137. ievent.bind()
  138. if ievent.hascc():
  139. ievent.iscommand = True
  140. ievent.showall = True
  141. ievent.nodispatch = False
  142. ievent.bind()
  143. self.put(ievent)
  144. continue
  145. elif ievent.txt[0] == "@":
  146. partyline.say_broadcast_notself(ievent.nick, "[%s] %s" % (ievent.nick, ievent.txt))
  147. q = Queue.Queue()
  148. ievent.queues = [q]
  149. ievent.txt = ievent.txt[1:]
  150. self.doevent(ievent)
  151. result = waitforqueue(q, 3000)
  152. if result:
  153. for i in result:
  154. partyline.say_broadcast("[bot] %s" % i)
  155. continue
  156. else:
  157. partyline.say_broadcast_notself(ievent.nick, "[%s] %s" % (ievent.nick, ievent.txt))
  158. except socket.error, ex:
  159. try:
  160. (errno, errstr) = ex
  161. except:
  162. errno = 0
  163. errstr = str(ex)
  164. if errno == 35 or errno == 11:
  165. continue
  166. except Exception, ex:
  167. handle_exception()
  168. sockfile.close()
  169. logging.warn('%s - closing dcc with %s' % (self.cfg.name, nick))
  170. def _dccconnect(self, nick, userhost, addr, port):
  171. """ connect to dcc request from nick. """
  172. try:
  173. port = int(port)
  174. logging.warn("%s - dcc - connecting to %s:%s (%s)" % (self.cfg.name, addr, port, userhost))
  175. if re.search(':', addr):
  176. sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  177. sock.connect((addr, port))
  178. else:
  179. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  180. sock.connect((addr, port))
  181. except Exception, ex:
  182. logging.error('%s - dcc error: %s' % (self.cfg.name, str(ex)))
  183. return
  184. self._dodcc(sock, nick, userhost, userhost)
  185. def broadcast(self, txt):
  186. """ broadcast txt to all joined channels. """
  187. for i in self.state['joinedchannels']: self.say(i, txt)
  188. def getchannelmode(self, channel):
  189. """ send MODE request for channel. """
  190. if not channel:
  191. return
  192. self.putonqueue(9, None, 'MODE %s' % channel)
  193. def join(self, channel, password=None):
  194. """ join a channel .. use optional password. """
  195. chan = ChannelBase(channel, self.cfg.name)
  196. if password:
  197. chan.data.key = password.strip()
  198. chan.save()
  199. result = Irc.join(self, channel, chan.data.key)
  200. if result != 1:
  201. return result
  202. got = False
  203. if not chan.data.cc:
  204. chan.data.cc = self.cfg.defaultcc or '!;'
  205. got = True
  206. if not chan.data.perms:
  207. chan.data.perms = []
  208. got = True
  209. if not chan.data.mode:
  210. chan.data.mode = ""
  211. got = True
  212. if got:
  213. chan.save()
  214. self.getchannelmode(channel)
  215. return 1
  216. def handle_privmsg(self, ievent):
  217. """ check if PRIVMSG is command, if so dispatch. """
  218. if ievent.nick in self.nicks401:
  219. logging.debug("%s - %s is available again" % (self.cfg,name, ievent.nick))
  220. self.nicks401.remove(ievent.nick)
  221. if not ievent.txt: return
  222. ievent.nodispatch = False
  223. chat = re.search(dccchatre, ievent.txt)
  224. if chat:
  225. if self.users.allowed(ievent.userhost, 'USER'):
  226. start_new_thread(self._dccconnect, (ievent.nick, ievent.userhost, chat.group(1), chat.group(2)))
  227. return
  228. if '\001' in ievent.txt:
  229. Irc.handle_privmsg(self, ievent)
  230. return
  231. ievent.bot = self
  232. ievent.sock = self.sock
  233. chan = ievent.channel
  234. if chan == self.cfg.nick:
  235. ievent.msg = True
  236. ievent.speed = 4
  237. ievent.printto = ievent.nick
  238. ccs = ['!', '@', self.cfg['defaultcc']]
  239. if ievent.isresponse:
  240. return
  241. if self.cfg['noccinmsg'] and self.msg:
  242. self.put(ievent)
  243. elif ievent.txt[0] in ccs:
  244. self.put(ievent)
  245. return
  246. self.put(ievent)
  247. def handle_join(self, ievent):
  248. """ handle joins. """
  249. if ievent.nick in self.nicks401:
  250. logging.debug("%s - %s is available again" % (self.cfg.name, ievent.nick))
  251. self.nicks401.remove(ievent.nick)
  252. chan = ievent.channel
  253. nick = ievent.nick
  254. if nick == self.cfg.nick:
  255. logging.warn("%s - joined %s" % (self.cfg.name, ievent.channel))
  256. time.sleep(0.5)
  257. self.who(chan)
  258. return
  259. logging.info("%s - %s joined %s" % (self.cfg.name, ievent.nick, ievent.channel))
  260. self.userhosts[nick] = ievent.userhost
  261. def handle_kick(self, ievent):
  262. """ handle kick event. """
  263. try:
  264. who = ievent.arguments[1]
  265. except IndexError:
  266. return
  267. chan = ievent.channel
  268. if who == self.cfg.nick:
  269. if chan in self.state['joinedchannels']:
  270. self.state['joinedchannels'].remove(chan)
  271. self.state.save()
  272. def handle_nick(self, ievent):
  273. """ update userhost cache on nick change. """
  274. nick = ievent.txt
  275. self.userhosts[nick] = ievent.userhost
  276. if ievent.nick == self.cfg.nick or ievent.nick == self.cfg.orignick:
  277. self.cfg['nick'] = nick
  278. self.cfg.save()
  279. def handle_part(self, ievent):
  280. """ handle parts. """
  281. chan = ievent.channel
  282. if ievent.nick == self.cfg.nick:
  283. logging.warn('%s - parted channel %s' % (self.cfg.name, chan))
  284. if chan in self.state['joinedchannels']:
  285. self.state['joinedchannels'].remove(chan)
  286. self.state.save()
  287. def handle_ievent(self, ievent):
  288. """ check for callbacks, call Irc method. """
  289. try:
  290. Irc.handle_ievent(self, ievent)
  291. if ievent.cmnd == 'JOIN' or ievent.msg:
  292. if ievent.nick in self.nicks401: self.nicks401.remove(ievent.nick)
  293. if ievent.cmnd != "PRIVMSG":
  294. i = IrcEvent()
  295. i.copyin(ievent)
  296. i.bot = self
  297. i.sock = self.sock
  298. ievent.nocb = True
  299. self.doevent(i)
  300. except:
  301. handle_exception()
  302. def handle_quit(self, ievent):
  303. """ check if quit is because of a split. """
  304. if '*.' in ievent.txt or self.cfg.server in ievent.txt: self.splitted.append(ievent.nick)
  305. def handle_mode(self, ievent):
  306. """ check if mode is about channel if so request channel mode. """
  307. logging.warn("%s - mode change %s" % (self.cfg.name, str(ievent.arguments)))
  308. try:
  309. dummy = ievent.arguments[2]
  310. except IndexError:
  311. chan = ievent.channel
  312. self.getchannelmode(chan)
  313. if not ievent.chan: ievent.bind(self)
  314. if ievent.chan:
  315. ievent.chan.data.mode = ievent.arguments[1]
  316. ievent.chan.save()
  317. def handle_311(self, ievent):
  318. """ handle 311 response .. sync with userhosts cache. """
  319. target, nick, user, host, dummy = ievent.arguments
  320. nick = nick
  321. userhost = "%s@%s" % (user, host)
  322. logging.debug('%s - adding %s to userhosts: %s' % (self.cfg.name, nick, userhost))
  323. self.userhosts[nick] = userhost
  324. def handle_352(self, ievent):
  325. """ handle 352 response .. sync with userhosts cache. """
  326. args = ievent.arguments
  327. channel = args[1]
  328. nick = args[5]
  329. user = args[2]
  330. host = args[3]
  331. userhost = "%s@%s" % (user, host)
  332. logging.debug('%s - adding %s to userhosts: %s' % (self.cfg.name, nick, userhost))
  333. self.userhosts[nick] = userhost
  334. def handle_353(self, ievent):
  335. """ handle 353 .. check if we are op. """
  336. userlist = ievent.txt.split()
  337. chan = ievent.channel
  338. for i in userlist:
  339. if i[0] == '@' and i[1:] == self.cfg.nick:
  340. if chan not in self.state['opchan']:
  341. self.state['opchan'].append(chan)
  342. def handle_324(self, ievent):
  343. """ handle mode request responses. """
  344. if not ievent.chan: ievent.bind(self, force=True)
  345. ievent.chan.data.mode = ievent.arguments[2]
  346. ievent.chan.save()
  347. def handle_invite(self, ievent):
  348. """ join channel if invited by OPER. """
  349. if self.users and self.users.allowed(ievent.userhost, ['OPER', ]): self.join(ievent.txt)
  350. def settopic(self, channel, txt):
  351. """ set topic of channel to txt. """
  352. self.putonqueue(7, None, 'TOPIC %s :%s' % (channel, txt))
  353. def gettopic(self, channel, event=None):
  354. """ get topic data. """
  355. q = Queue.Queue()
  356. i332 = waiter.register("332", queue=q)
  357. i333 = waiter.register("333", queue=q)
  358. self.putonqueue(7, None, 'TOPIC %s' % channel)
  359. res = waitforqueue(q, 5000)
  360. who = what = when = None
  361. for r in res:
  362. if not r.postfix: continue
  363. try:
  364. if r.cmnd == "332": what = r.txt ; waiter.ready(i332) ; continue
  365. waiter.ready(i333)
  366. splitted = r.postfix.split()
  367. who = splitted[2]
  368. when = float(splitted[3])
  369. except (IndexError, ValueError): continue
  370. return (what, who, when)
  371. def _onconnect(self):
  372. m = self.cfg.servermodes
  373. if m:
  374. logging.warn("sending servermodes %s" % m)
  375. self._raw("MODE %s %s" % (self.cfg.nick, m))