1
0

bot.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #! /usr/bin/env python
  2. import sys, ssl, random#, string,
  3. import irc.bot#, irc.strings #https://python-irc.readthedocs.io/en/latest/irc.html#
  4. #from irc.client import ip_numstr_to_quad#, ip_quad_to_numstr
  5. from jaraco.stream import buffer
  6. from postgres import Postgres # https://postgres-py.readthedocs.io/en/latest/
  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, font, queries
  10. from common.networkservices import NickServ
  11. from events.common import Inform #, MessageStatistics #Lastact,
  12. class PyRot(irc.bot.SingleServerIRCBot):
  13. def __init__(self, network, db, webgui):
  14. # Globals, so it's not needed to pass network and db with every call.
  15. self.network = network
  16. self.db = db
  17. self.webgui = webgui
  18. # Pick a random host from the network.
  19. hosts = db.all("SELECT * FROM rotbot_host WHERE network_id=%(network)s", network=network.id)
  20. host = random.choice(hosts)
  21. connect_string = (host.address, host.port)
  22. irc.client.ServerConnection.buffer_class = buffer.LenientDecodingLineBuffer # Set buffer class.
  23. # self.protectees = {}
  24. # self.channelkeys = {}
  25. # Log and record to database.
  26. log.info('Connecting to %s:%s/%s' % (connect_string[0], connect_string[1], network.home_channel))
  27. db.run("UPDATE rotbot_host SET connection_attempts = connection_attempts + 1 WHERE id=%s", [network.id])
  28. # Create connect factory with correct scheme.
  29. if host.ssl:
  30. factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
  31. else:
  32. factory = irc.connection.Factory()
  33. try:
  34. irc.bot.SingleServerIRCBot.__init__(self, [connect_string], network.nickname, network.username, connect_factory=factory) # Connect.
  35. except irc.client.ServerConnectionError: # Connection failure.
  36. sys.exit(sys.exc_info()[1]) # Exit.
  37. ## Events.
  38. def on_nicknameinuse(self, connection, event):
  39. log.info('Nickname %s in use, attempting to recover: %s' % (self.network.nickname, connection.nickname))
  40. connection.nick(connection.nickname + ''.join(random.choice(string.digits) for _ in range(3))) # Take temporary nick. In this state recovering via NickServ won't work without changing nickname.
  41. NickServ.recover_nick(connection, self.network.password)
  42. def on_welcome(self, connection, event):
  43. events.on_welcome.process_event(self, connection, event)
  44. #
  45. def on_error(self, connection, event):
  46. log.notice(str(event))
  47. connection.privmsg(self.homechannel, 'ERROR: %s' %s (event))
  48. Inform.notice_owners(self, connection, 'Received error from server: %s' % (event))
  49. #
  50. # def on_nick(self, connection, event):
  51. # events.on_nick.process_event(self, connection, event)
  52. #
  53. def on_join(self, connection, event):
  54. events.on_join.process_event(self, connection, event)
  55. def on_mode(self, connection, event):
  56. events.on_mode.process_event(self, connection, event)
  57. def on_kick(self, connection, event):
  58. events.on_kick.process_event(self, connection, event)
  59. #
  60. # def on_part(self, connection, event):
  61. # log.info(event)
  62. #
  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. #
  67. #
  68. # def on_quit(self, connection, event):
  69. # log.info(event)
  70. #
  71. # # Update protectees.
  72. # if event.source.nick in self.protectees: # Protectee parted home channel.
  73. # del self.protectees[event.source.nick] # Delete from protectees.
  74. #
  75. def on_invite(self, connection, event):
  76. log.notice(event)
  77. if event.target == connection.get_nickname(): # Bot invited.
  78. Inform.operators(self, connection, 'Received invitation to %s %s %s form %s %s %s.' % font.red, event.arguments[0], font.reset, font.red, event.source.nick, font.reset)
  79. #
  80. # def on_topic(self, connection, event):
  81. # log.info(event)
  82. #
  83. #
  84. def on_pubmsg(self, connection, event): # Channel message
  85. events.on_pubmsg.process_event(self, connection, event)
  86. def on_action(self, connection, event): # Is this both channel and private actions?
  87. events.on_action.process_event(self, connection, event)
  88. def on_privmsg(self, connection, event): # Private message
  89. log.info(event)
  90. user = queries.create_or_get_and_update_last_event(self, 'user', 'pm', user_name=event.source.nick, event_subject_name=event.target)
  91. channel = None
  92. if not event.source.nick == connection.get_nickname(): # Message is not from myself.
  93. Inform.owners(self, connection, 'PM from %s%s%s: %s%s' % (font.red, event.source.nick, font.grey, font.green, event.arguments[0])) # Forward message to users with owner rights of home channel.
  94. commands.public.do_command(self, connection, event, user, channel)
  95. commands.admin.do_command(self, connection, event, user, channel)
  96. commands.statistics.do_command(self, connection, event, user, channel)
  97. commands.games.do_command(self, connection, event, user, channel)
  98. def on_pubnotice(self, connection, event): # Channel notice
  99. log.info(event)
  100. user = queries.create_or_get_and_update_last_event(self, 'user', 'cn', channel_name=event.target, user_name=event.source.nick, event_content=event.arguments[0])
  101. channel = queries.create_or_get_and_update_last_event(self, 'channel', 'cn', channel_name=event.target, user_name=event.source.nick, event_content=event.arguments[0])
  102. def on_privnotice(self, connection, event): # Private notice.
  103. log.info(event)
  104. user = queries.create_or_get_and_update_last_event(self, 'user', 'pn', user_name=event.source.nick, event_subject_name=event.target)
  105. channel = None
  106. commands.public.do_command(self, connection, event, user, channel)
  107. commands.admin.do_command(self, connection, event, user, channel)
  108. commands.statistics.do_command(self, connection, event, user, channel)
  109. commands.games.do_command(self, connection, event, user, channel)
  110. if event.source.nick == connection.get_nickname(): # Message came from myself.
  111. return
  112. elif event.source.nick == "NickServ":
  113. if event.arguments[0].startswith("This nickname is registered"):
  114. connection.privmsg('NickServ', 'identify %s %s' % (self.network.nickname, self.network.password)) # Identify with NickServ.
  115. if event.arguments[0].startswith("You are already identified."):
  116. return
  117. # Nick \x02RotBot\x02 isn't registered.
  118. if event.arguments[0].endswith(' is not a registered nickname.') or event.arguments[0].startswith('Nick ') and event.arguments[0].endswith(' isn\'t registered.') or event.arguments[0] == 'Your nick isn\'t registered.': # Username from database is not registered.
  119. connection.privmsg('NickServ', 'register %s spamtBK@xs4all.nl' % (self.network.password)) # Register with NickServ.
  120. connection.privmsg(self.network.home_channel, 'Regisring %s%s%s with %sNickServ%s.' % (font.red, self.network.nickname, font.reset, font.red, font.reset)) # Recover control of nickname via NickServ, old style.
  121. log.info('Registerring with NickServ.')
  122. if event.arguments[0].startswith('Nickname ') and event.arguments[0].endswith(' registered.'):
  123. Inform.home_channel(seld, connection, 'Registerred nickname %s%s%s with NickServ.' % font.red, self.network.nickname, font.reset)
  124. # elif event.source.nick == "ChanServ":
  125. # if event.arguments[0].startswith("Key for channel ") and len(event.arguments[0]) > 5: # Received channel key.
  126. # self.channelkeys[event.arguments[0].split(' ')[3]] = event.arguments[0].split(' ')[5][:-1]
  127. # connection.join(event.arguments[0].split(' ')[3], event.arguments[0].split(' ')[5][:-1])
  128. # Inform.owners(self, connection, "Received " + red + event.arguments[0].split(" ")[3] + reset + " key: " + event.arguments.split(" ")[5][:-1])
  129. # if event.arguments[0] == "Password authentication required for that command.": # Not authenticated with NisckServ.
  130. # Inform.notice_owners(self, connection, "Not authenticated with NickServ. See " + blue + self.helpchar + "recovernick " + reset + "and " + blue + self.helpchar + "registernick" + reset + ".")
  131. # return
  132. # if event.arguments[0].startswith("You have been unbanned from ") or event.arguments[0].endswith(" autokick list is empty.") or event.arguments[0].startswith("You are already in ") or event.arguments[0] == "Syntax: UNBAN channel [nick]" or event.arguments[0] == "/msg ChanServ HELP UNBAN for more information":
  133. # return
  134. # if event.arguments[0].startswith("Channel ") and event.arguments[0].endswith(" has no key."):
  135. # return
  136. if event.source.nick != "Global":
  137. Inform.notice_owners(self, connection, 'Notice from %s %s %s %s: %s %s' % (font.red, font.red, event.source.nick, font.grey, font.reset, event.arguments[0]))
  138. #
  139. # def on_whoreply(self, connection, event):
  140. # events.on_whoreply.process_event(self, connection, event)
  141. #
  142. def on_keyset(self, connection, event):
  143. log.info(event)
  144. Inform.owners(self, connection, "keyset " + font.green + event.arguments[0] + reset.reset + " from " + font.red + event.source)
  145. def on_yourhost(self, connection, event):
  146. log.info(event)
  147. def on_yourebannedcreep(self, connection, event):
  148. Inform.operators(self, connection, "I am banned " + font.red + event.arguments[0] + reset.reset + " from " + font.red + event.source)
  149. log.warning(event)
  150. def on_youwillbebanned(self, connection, event):
  151. log.warning(event)
  152. Inform.operators(self, connection, "I will be banned " + font.red + event.arguments[0] + reset.reset + " from " + font.red + event.source)
  153. # DCC stuff from originalexample file.
  154. def on_dccmsg(self, c, e):
  155. log.info(e)
  156. # non-chat DCC messages are raw bytes; decode as text
  157. #text = e.arguments[0].decode('utf-8')
  158. #c.privmsg("You said: " + text)
  159. def on_dccchat(self, c, e):
  160. log.info(e)
  161. # if len(e.arguments) != 2:
  162. # return
  163. # args = e.arguments[1].split()
  164. # if len(args) == 4:
  165. # try:
  166. # address = ip_numstr_to_quad(args[2])
  167. # port = int(args[3])
  168. # except ValueError:
  169. # return
  170. # self.dcc_connect(address, port)
  171. def main():
  172. log.info('Starting RotBot: pyRot version.')
  173. # Validate system arguments and warn if needed..
  174. if len(sys.argv) != 2: # Not 2 arguments.
  175. sys.exit('To run type "python bot.py database_network_id", not: %s. Terminating program.' % (sys.argv)) # Terminate program.
  176. instance = sys.argv[1] # Instance is the database network id.
  177. ### SETTINGS
  178. webgui = {
  179. 'base_url': 'https://h0v1n8.nl/rotbot/', # The django rotbot app url.
  180. 'register_url': '',
  181. }
  182. # Database credentials
  183. username = 'pyrot'
  184. password = 'oGPnbiqh55QKLhmnKQgS92h74j0e9d6LE58cSsD1'
  185. db_connect_string = 'postgres://%s:%s@localhost/website' % (username, password)
  186. try:
  187. db = Postgres(db_connect_string) # Connect to database.
  188. except:
  189. sys.exit('Database connection %s failed: %s' % (db_connect_string, sys.exc_info())) # Exit.
  190. try:
  191. network = db.one("SELECT * FROM rotbot_network WHERE id=%(instance)s", instance=instance) # Get network from database.
  192. except:
  193. sys.exit('Could not retrieve network %s from database: %s' % (instance, sys.exc_info())) # Exit.
  194. if network == None: # No data returned.
  195. sys.exit('Network %s not found, terminating program.' % (instance)) # Exit.
  196. if network.enabled == False: # Network disabled in database.
  197. sys.exit('Network %s marked as disabled in database, use webgui to enable. Terminating program.' % (network.name)) # Exit
  198. bot = PyRot(network, db, webgui)
  199. bot.start()
  200. if __name__ == "__main__":
  201. try:
  202. main() # Run the program.
  203. except KeyboardInterrupt: # User presses CTRL+C
  204. sys.exit('Interrupted by keyboard.') # Exit.