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.

162 line
5.2 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. EarwigBot's JSON Config File Parser
  4. This handles all tasks involving reading and writing to our config file,
  5. including encrypting and decrypting passwords and making a new config file from
  6. scratch at the inital bot run.
  7. Usually you'll just want to do "from core import config" and access config data
  8. from within config's three global variables and one function:
  9. * config.components - a list of enabled components
  10. * config.wiki - a dict of config information for wiki-editing
  11. * config.irc - a dict of config information for IRC
  12. * config.schedule() - returns a list of tasks scheduled to run at a given
  13. time
  14. """
  15. import json
  16. from os import makedirs, path
  17. from lib import blowfish
  18. script_dir = path.dirname(path.abspath(__file__))
  19. root_dir = path.split(script_dir)[0]
  20. config_path = path.join(root_dir, "config.json")
  21. _config = None # holds data loaded from our config file
  22. # set our three easy-config-access global variables to None
  23. components, wiki, irc = (None, None, None)
  24. def is_config_loaded():
  25. """Return True if our config file has already been loaded, and False if it
  26. hasn't."""
  27. if _config is not None:
  28. return True
  29. return False
  30. def load_config():
  31. """Load data from our JSON config file (config.json) into _config."""
  32. global _config
  33. with open(config_path, 'r') as fp:
  34. try:
  35. _config = json.load(fp)
  36. except ValueError as error:
  37. print "Error parsing config file {0}:".format(config_path)
  38. print error
  39. exit(1)
  40. def verify_config():
  41. """Check to see if we have a valid config file, and if not, notify the
  42. user. If there is no config file at all, offer to make one; otherwise,
  43. exit. If everything goes well, return True if stored passwords are
  44. encrypted in the file, or False if they are not."""
  45. if path.exists(config_path):
  46. load_config()
  47. try:
  48. return _config["encryptPasswords"] # are passwords encrypted?
  49. except KeyError:
  50. return False # assume passwords are not encrypted by default
  51. else:
  52. print "You haven't configured the bot yet!"
  53. choice = raw_input("Would you like to do this now? [y/n] ")
  54. if choice.lower().startswith("y"):
  55. return make_new_config()
  56. else:
  57. exit(1)
  58. def parse_config(key):
  59. """Store data from our config file in three global variables for easy
  60. access, and use the key to unencrypt passwords. Catch password decryption
  61. errors and report them to the user."""
  62. global components, wiki, irc
  63. load_config() # we might be re-loading unnecessarily here, but no harm in
  64. # that!
  65. try:
  66. components = _config["components"]
  67. except KeyError:
  68. components = []
  69. try:
  70. wiki = _config["wiki"]
  71. except KeyError:
  72. wiki = {}
  73. try:
  74. irc = _config["irc"]
  75. except KeyError:
  76. irc = {}
  77. try:
  78. try:
  79. if _config["encryptPasswords"]:
  80. decrypt(key, "wiki['password']")
  81. decrypt(key, "irc['frontend']['nickservPassword']")
  82. decrypt(key, "irc['watcher']['nickservPassword']")
  83. except KeyError:
  84. pass
  85. except blowfish.BlowfishError as error:
  86. print "\nError decrypting passwords:"
  87. print "{0}: {1}.".format(error.__class__.__name__, error)
  88. exit(1)
  89. def decrypt(key, item):
  90. """Decrypt 'item' with blowfish.decrypt() using the given key and set it to
  91. the decrypted result. 'item' should be a string, like
  92. decrypt(key, "wiki['password']"), NOT decrypt(key, wiki['password'),
  93. because that won't work."""
  94. global irc, wiki
  95. try:
  96. result = blowfish.decrypt(key, eval(item))
  97. except KeyError:
  98. return
  99. exec "{0} = result".format(item)
  100. def schedule(minute, hour, month_day, month, week_day):
  101. """Return a list of tasks that are scheduled to run at the time specified
  102. by the function arguments. The schedule data comes from our config file's
  103. 'schedule' field, which is stored as _config["schedule"]. Call this
  104. function with config.schedule(args)."""
  105. tasks = [] # tasks to run this turn, each as a tuple of either (task_name,
  106. # kwargs), or just task_name
  107. now = {"minute": minute, "hour": hour, "month_day": month_day,
  108. "month": month, "week_day": week_day}
  109. try:
  110. data = _config["schedule"]
  111. except KeyError:
  112. return [] # nothing is in our schedule
  113. for event in data:
  114. do = True
  115. for key, value in now.items():
  116. try:
  117. requirement = event[key]
  118. except KeyError:
  119. continue
  120. if requirement != value:
  121. do = False
  122. break
  123. if do:
  124. try:
  125. tasks.extend(event["tasks"])
  126. except KeyError:
  127. pass
  128. return tasks
  129. def make_new_config():
  130. """Make a new config file based on the user's input."""
  131. makedirs(config_dir)
  132. encrypt = raw_input("Would you like to encrypt passwords stored in " +
  133. "config.json? [y/n] ")
  134. if encrypt.lower().startswith("y"):
  135. is_encrypted = True
  136. else:
  137. is_encrypted = False
  138. return is_encrypted