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.

143 lines
5.0 KiB

  1. #! /usr/bin/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.
  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. try:
  88. key = raw_input() # wait for our password unlock key from the bot's
  89. except EOFError: # wrapper
  90. key = None
  91. config.parse_config(key) # load data from the config file and parse it
  92. # using the unlock key
  93. enabled = config.components
  94. if "irc_frontend" in enabled: # make the frontend run on our primary
  95. irc_frontend() # thread if enabled, and enable additional
  96. # components through that function
  97. elif "wiki_schedule" in enabled: # run the scheduler on the main
  98. print "Starting wiki scheduler..." # thread, but also run the IRC
  99. tasks.load() # watcher on another thread iff it
  100. if "irc_watcher" in enabled: # is enabled
  101. print "\nStarting IRC watcher..."
  102. t_watcher = threading.Thread(target=irc_watcher, args=())
  103. t_watcher.name = "irc-watcher"
  104. t_watcher.daemon = True
  105. t_watcher.start()
  106. wiki_scheduler()
  107. elif "irc_watcher" in enabled: # the IRC watcher is our only enabled
  108. print "Starting IRC watcher..." # component, so run its function only
  109. irc_watcher() # and don't worry about anything else
  110. else: # nothing is enabled!
  111. print "No bot parts are enabled; stopping..."
  112. exit(1)
  113. if __name__ == "__main__":
  114. try:
  115. run()
  116. except KeyboardInterrupt:
  117. print "\nKeyboardInterrupt: stopping main bot loop."
  118. exit(1)