bot.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. #! /usr/bin/env python
  2. import sys, random, string, ssl
  3. import irc.bot#, irc.strings
  4. from irc.client import ip_numstr_to_quad#, ip_quad_to_numstr
  5. from jaraco.stream import buffer
  6. from postgres import Postgres
  7. import commands.public, commands.admin, commands.games, commands.statistics
  8. import events.on_welcome, events.on_join, events.on_kick, events.on_mode, events.on_pubmsg, events.on_action, events.on_whoreply, events.on_nick
  9. from common import log
  10. from common.networkservices import NickServ
  11. from events.common import Lastact, MessageStatistics, Inform
  12. bold = "\x02"
  13. italic = "\x1D"
  14. underline = "\x1F"
  15. reverse = "\x16" # swap background and foreground colors ("reverse video")
  16. reset = "\x0F"
  17. blue = "\x0302"
  18. green = "\x0303"
  19. red = "\x0304"
  20. grey = "\x0314"
  21. class PyRot(irc.bot.SingleServerIRCBot):
  22. def __init__(self, network, db, homechannel, nickname, username, password, host, port=6667, usessl=False, cmdchar="!", helpchar="@"):
  23. self.network = network
  24. self.db = db
  25. self.homechannel = homechannel
  26. self.password = password
  27. self.cmdchar = cmdchar
  28. self.helpchar = helpchar
  29. self.protectees = {}
  30. self.channelkeys = {}
  31. log.info("Starting pyRot, the third RotBot by tBkwtWS.")
  32. log.info("Connecting to " + host + ":" + str(port) + "/" + self.homechannel)
  33. if usessl:
  34. factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
  35. else:
  36. factory = irc.connection.Factory()
  37. irc.client.ServerConnection.buffer_class = buffer.LenientDecodingLineBuffer
  38. try:
  39. irc.bot.SingleServerIRCBot.__init__(self, [(host, port)], nickname, username, connect_factory=factory)
  40. except irc.client.ServerConnectionError:
  41. sys.stderr.write(sys.exc_info()[1])
  42. # Events.
  43. def on_nicknameinuse(self, connection, event):
  44. log.info("Nickname in use, attempting to recover: " + connection.nickname)
  45. connection.nick(connection.nickname + ''.join(random.choice(string.digits) for _ in range(3))) # Take temporary nick. Without this recovering via NickServ won't work.
  46. NickServ.recover_nick(connection, self.password)
  47. def on_welcome(self, connection, event):
  48. events.on_welcome.process_event(self, connection, event)
  49. def on_error(self, connection, event):
  50. log.notice(str(event))
  51. connection.privmsg(self.homechannel, "ERROR: " + str(event))
  52. Inform.owners(self, connection, "Received error from server: " + str(event))
  53. def on_nick(self, connection, event):
  54. events.on_nick.process_event(self, connection, event)
  55. def on_join(self, connection, event):
  56. events.on_join.process_event(self, connection, event)
  57. def on_kick(self, connection, event):
  58. events.on_kick.process_event(self, connection, event)
  59. def on_mode(self, connection, event):
  60. events.on_mode.process_event(self, connection, event)
  61. def on_part(self, connection, event):
  62. log.info(event)
  63. # Update protectees.
  64. if event.target == self.homechannel and event.source.nick in self.protectees: # Protectee parted home channel.
  65. del self.protectees[event.source.nick] # Delete from protectees.
  66. # Update last act.
  67. if event.arguments:
  68. Lastact.update(self, event.source.nick, "part", channel=event.target, lastact=event.arguments[0])
  69. else:
  70. Lastact.update(self, event.source.nick, "part", channel=event.target)
  71. def on_quit(self, connection, event):
  72. log.info(event)
  73. # Update protectees.
  74. if event.source.nick in self.protectees: # Protectee parted home channel.
  75. del self.protectees[event.source.nick] # Delete from protectees.
  76. # Update last act.
  77. if event.arguments:
  78. Lastact.update(self, event.source.nick, "quit", lastact=event.arguments[0])
  79. else:
  80. Lastact.update(self, event.source.nick, "quit")
  81. def on_invite(self, connection, event):
  82. log.notice(event)
  83. if event.target == connection.get_nickname(): # Bot invited.
  84. Inform.operators(self, connection, "Received invitation to " + red + event.arguments[0] + reset + " from " + red + event.source.nick + reset + ".")
  85. def on_topic(self, connection, event):
  86. log.info(event)
  87. # Update last act.
  88. Lastact.update(self, event.source.nick, "topic", channel=event.target, lastact=event.arguments[0])
  89. def on_pubmsg(self, connection, event):
  90. events.on_pubmsg.process_event(self, connection, event)
  91. commands.public.do_command(self, connection, event)
  92. commands.admin.do_command(self, connection, event)
  93. commands.statistics.do_command(self, connection, event)
  94. commands.games.do_command(self, connection, event)
  95. def on_privmsg(self, connection, event):
  96. log.info(event)
  97. commands.public.do_command(self, connection, event)
  98. commands.admin.do_command(self, connection, event)
  99. commands.statistics.do_command(self, connection, event)
  100. commands.games.do_command(self, connection, event)
  101. if not event.source == connection.get_nickname():
  102. if event.arguments[0] == "Password authentication required for that command.":
  103. return
  104. Inform.owners(self, connection, "PM from " + red + red + event.source.nick + grey + ": " + reset + event.arguments[0] + ".")
  105. def on_pubnotice(self, connection, event):
  106. log.info(event)
  107. # Update last act.
  108. Lastact.update(self, event.source.nick, "notice", channel=event.target, lastact=event.arguments[0])
  109. # Save statistic to database.
  110. MessageStatistics.update(self, event, "notice")
  111. def on_privnotice(self, connection, event):
  112. log.info(event)
  113. commands.public.do_command(self, connection, event)
  114. commands.admin.do_command(self, connection, event)
  115. commands.statistics.do_command(self, connection, event)
  116. commands.games.do_command(self, connection, event)
  117. try:
  118. if event.source.nick == "NickServ" and event.arguments[0].startswith("This nickname is registered"):
  119. connection.privmsg("NickServ", "identify " + connection.nickname + " " + self.password) # Identify with NickServ.
  120. if event.source.nick == "ChanServ" and event.arguments[0].startswith("Key for channel ") and len(event.arguments[0]) > 5: # Received channel key.
  121. connection.join(event.arguments[0].split(' ')[3], event.arguments[0].split(' ')[5][:-1])
  122. except:
  123. pass
  124. Inform.owners(self, connection, "PM from " + red + red + event.source.nick + grey + ": " + reset + event.arguments[0] + ".")
  125. def on_action(self, connection, event):
  126. events.on_action.process_event(self, connection, event)
  127. def on_whoreply(self, connection, event):
  128. events.on_whoreply.process_event(self, connection, event)
  129. def on_userhost(self, connection, event):
  130. log.info(event)
  131. def on_yourhost(self, connection, event):
  132. log.info(event)
  133. def on_yourebannedcreep(self, connection, event):
  134. log.warning(event)
  135. def on_youwillbebanned(self, connection, event):
  136. log.warning(event)
  137. Inform.operators(self, connection, "I will be banned " + red + event.arguments[0] + reset + " from " + red + event.source)
  138. # DCC stuff from originalexample file.
  139. def on_dccmsg(self, c, e):
  140. log.info(e)
  141. # non-chat DCC messages are raw bytes; decode as text
  142. text = e.arguments[0].decode('utf-8')
  143. c.privmsg("You said: " + text)
  144. def on_dccchat(self, c, e):
  145. log.info(e)
  146. if len(e.arguments) != 2:
  147. return
  148. args = e.arguments[1].split()
  149. if len(args) == 4:
  150. try:
  151. address = ip_numstr_to_quad(args[2])
  152. port = int(args[3])
  153. except ValueError:
  154. return
  155. self.dcc_connect(address, port)
  156. def main():
  157. # Check system arguments.
  158. if len(sys.argv) != 2:
  159. print(sys.argv)
  160. print("Usage: rotbot <server ID from database>")
  161. sys.exit(1)
  162. instance = sys.argv[1] # Instance is the database network id.
  163. # Database.
  164. db = Postgres("postgres://pyRot:4h8q(.@localhost/pyRot")
  165. # Get network from database.
  166. try:
  167. network = db.one("SELECT * FROM networks WHERE id=" + str(instance))
  168. except:
  169. print("Invalid network ID.")
  170. sys.exit(1)
  171. if network == None:
  172. print("Invalid network ID.")
  173. sys.exit(1)
  174. bot = PyRot(network.name, db, network.home_channel, network.nickname, network.username, network.password, network.host, network.port, network.use_ssl, network.command_character, network.help_character)
  175. bot.start()
  176. if __name__ == "__main__":
  177. try:
  178. main()
  179. except KeyboardInterrupt:
  180. log.info('Interrupted by keyboard.')
  181. sys.exit(0)