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.

151 lines
4.9 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 logging
  28. import threading
  29. import time
  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. try:
  44. watcher.main(w_conn, f_conn)
  45. except:
  46. logging.exception("Watcher had an error")
  47. time.sleep(5) # sleep a bit before restarting watcher
  48. logging.warn("Watcher has stopped; restarting component")
  49. def wiki_scheduler():
  50. """Function to handle the wiki scheduler as another thread, or as the
  51. primary thread if the IRC frontend is not enabled."""
  52. while 1:
  53. time_start = time.time()
  54. now = time.gmtime(time_start)
  55. tasks.schedule(now)
  56. time_end = time.time()
  57. time_diff = time_start - time_end
  58. if time_diff < 60: # sleep until the next minute
  59. time.sleep(60 - time_diff)
  60. def irc_frontend():
  61. """If the IRC frontend is enabled, make it run on our primary thread, and
  62. enable the wiki scheduler and IRC watcher on new threads if they are
  63. enabled."""
  64. global f_conn
  65. logging.info("Starting IRC frontend")
  66. f_conn = frontend.get_connection()
  67. frontend.startup(f_conn)
  68. if "wiki_schedule" in config.components:
  69. logging.info("Starting wiki scheduler")
  70. tasks.load()
  71. t_scheduler = threading.Thread(target=wiki_scheduler)
  72. t_scheduler.name = "wiki-scheduler"
  73. t_scheduler.daemon = True
  74. t_scheduler.start()
  75. if "irc_watcher" in config.components:
  76. logging.info("Starting IRC watcher")
  77. t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,))
  78. t_watcher.name = "irc-watcher"
  79. t_watcher.daemon = True
  80. t_watcher.start()
  81. frontend.main()
  82. if "irc_watcher" in config.components:
  83. w_conn.close()
  84. f_conn.close()
  85. def run():
  86. config.load()
  87. try:
  88. # Wait for our password decrypt key from the bot's wrapper, then
  89. # decrypt passwords:
  90. key = raw_input()
  91. except EOFError:
  92. pass
  93. else:
  94. config.decrypt(key)
  95. enabled = config.components
  96. if "irc_frontend" in enabled:
  97. # Make the frontend run on our primary thread if enabled, and enable
  98. # additional components through that function
  99. irc_frontend()
  100. elif "wiki_schedule" in enabled:
  101. # Run the scheduler on the main thread, but also run the IRC watcher on
  102. # another thread iff it is enabled
  103. logging.info("Starting wiki scheduler")
  104. tasks.load()
  105. if "irc_watcher" in enabled:
  106. logging.info("Starting IRC watcher")
  107. t_watcher = threading.Thread(target=irc_watcher)
  108. t_watcher.name = "irc-watcher"
  109. t_watcher.daemon = True
  110. t_watcher.start()
  111. wiki_scheduler()
  112. elif "irc_watcher" in enabled:
  113. # The IRC watcher is our only enabled component, so run its function
  114. # only and don't worry about anything else:
  115. logging.info("Starting IRC watcher")
  116. irc_watcher()
  117. else: # Nothing is enabled!
  118. logging.critical("No bot parts are enabled; stopping")
  119. exit(1)
  120. if __name__ == "__main__":
  121. try:
  122. run()
  123. except KeyboardInterrupt:
  124. logging.critical("KeyboardInterrupt: stopping main bot loop")
  125. exit(1)