A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
5.0 KiB

  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. EarwigBot's Core
  5. This should not be run directly; the wrapper in "earwigbot.py" is preferred,
  6. but it should work fine alone, as long as you enter the password-unlock key at
  7. the initial hidden prompt if one is needed.
  8. The core is essentially responsible for starting the various bot components
  9. (irc, scheduler, etc) and making sure they are all happy. An explanation of the
  10. different components follows:
  11. EarwigBot has three components that can run independently of each other: an IRC
  12. front-end, an IRC watcher, and a wiki scheduler.
  13. * The IRC front-end runs on a normal IRC server and expects users to interact
  14. with it/give it commands.
  15. * The IRC watcher runs on a wiki recent-changes server and listens for edits.
  16. Users cannot interact with this part of the bot.
  17. * The wiki scheduler runs wiki-editing bot tasks in separate threads at
  18. user-defined times through a cron-like interface.
  19. There is a "priority" system here:
  20. 1. If the IRC frontend is enabled, it will run on the main thread, and the IRC
  21. watcher and wiki scheduler (if enabled) will run on separate threads.
  22. 2. If the wiki scheduler is enabled, it will run on the main thread, and the
  23. IRC watcher (if enabled) will run on a separate thread.
  24. 3. If the IRC watcher is enabled, it will run on the main (and only) thread.
  25. Else, the bot will stop, as no components are enabled.
  26. """
  27. import threading
  28. import time
  29. import traceback
  30. import config
  31. import frontend
  32. import tasks
  33. import watcher
  34. f_conn = None
  35. w_conn = None
  36. def irc_watcher(f_conn=None):
  37. """Function to handle the IRC watcher as another thread (if frontend and/or
  38. scheduler is enabled), otherwise run as the main thread."""
  39. global w_conn
  40. while 1: # restart the watcher component if it breaks (and nothing else)
  41. w_conn = watcher.get_connection()
  42. w_conn.connect()
  43. print # blank line to signify that the bot has finished starting up
  44. try:
  45. watcher.main(w_conn, f_conn)
  46. except:
  47. traceback.print_exc()
  48. time.sleep(5) # sleep a bit before restarting watcher
  49. print "\nWatcher has stopped; restarting component..."
  50. def wiki_scheduler():
  51. """Function to handle the wiki scheduler as another thread, or as the
  52. primary thread if the IRC frontend is not enabled."""
  53. while 1:
  54. time_start = time.time()
  55. now = time.gmtime(time_start)
  56. tasks.schedule(now)
  57. time_end = time.time()
  58. time_diff = time_start - time_end
  59. if time_diff < 60: # sleep until the next minute
  60. time.sleep(60 - time_diff)
  61. def irc_frontend():
  62. """If the IRC frontend is enabled, make it run on our primary thread, and
  63. enable the wiki scheduler and IRC watcher on new threads if they are
  64. enabled."""
  65. global f_conn
  66. print "Starting IRC frontend..."
  67. f_conn = frontend.get_connection()
  68. frontend.startup(f_conn)
  69. if "wiki_schedule" in config.components:
  70. print "\nStarting wiki scheduler..."
  71. tasks.load()
  72. t_scheduler = threading.Thread(target=wiki_scheduler)
  73. t_scheduler.name = "wiki-scheduler"
  74. t_scheduler.daemon = True
  75. t_scheduler.start()
  76. if "irc_watcher" in config.components:
  77. print "\nStarting IRC watcher..."
  78. t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,))
  79. t_watcher.name = "irc-watcher"
  80. t_watcher.daemon = True
  81. t_watcher.start()
  82. frontend.main()
  83. if "irc_watcher" in config.components:
  84. w_conn.close()
  85. f_conn.close()
  86. def run():
  87. config.load()
  88. try:
  89. key = raw_input() # wait for our password decrypt key from the bot's
  90. except EOFError: # wrapper, then decrypt passwords
  91. pass
  92. else:
  93. config.decrypt(key)
  94. enabled = config.components
  95. if "irc_frontend" in enabled: # make the frontend run on our primary
  96. irc_frontend() # thread if enabled, and enable additional
  97. # components through that function
  98. elif "wiki_schedule" in enabled: # run the scheduler on the main
  99. print "Starting wiki scheduler..." # thread, but also run the IRC
  100. tasks.load() # watcher on another thread iff it
  101. if "irc_watcher" in enabled: # is enabled
  102. print "\nStarting IRC watcher..."
  103. t_watcher = threading.Thread(target=irc_watcher)
  104. t_watcher.name = "irc-watcher"
  105. t_watcher.daemon = True
  106. t_watcher.start()
  107. wiki_scheduler()
  108. elif "irc_watcher" in enabled: # the IRC watcher is our only enabled
  109. print "Starting IRC watcher..." # component, so run its function only
  110. irc_watcher() # and don't worry about anything else
  111. else: # nothing is enabled!
  112. print "No bot parts are enabled; stopping..."
  113. exit(1)
  114. if __name__ == "__main__":
  115. try:
  116. run()
  117. except KeyboardInterrupt:
  118. print "\nKeyboardInterrupt: stopping main bot loop."
  119. exit(1)