| @@ -442,8 +442,8 @@ class Blowfish(object): | |||||
| def encrypt(self, data): | def encrypt(self, data): | ||||
| if not len(data) == 8: | if not len(data) == 8: | ||||
| raise BlockSizeError("blocks must be 8 bytes long, but tried to " + | |||||
| "encrypt one {0} bytes long".format(len(data))) | |||||
| e = "blocks must be 8 bytes long, but tried to encrypt one {0} bytes long" | |||||
| raise BlockSizeError(e.format(len(data))) | |||||
| # Use big endianess since that's what everyone else uses | # Use big endianess since that's what everyone else uses | ||||
| xl = ord (data[3]) | (ord (data[2]) << 8) | (ord (data[1]) << 16) | (ord (data[0]) << 24) | xl = ord (data[3]) | (ord (data[2]) << 8) | (ord (data[1]) << 16) | (ord (data[0]) << 24) | ||||
| @@ -458,8 +458,8 @@ class Blowfish(object): | |||||
| def decrypt(self, data): | def decrypt(self, data): | ||||
| if not len(data) == 8: | if not len(data) == 8: | ||||
| raise BlockSizeError("blocks must be 8 bytes long, but tried to " + | |||||
| "decrypt one {0} bytes long".format(len(data))) | |||||
| e = "blocks must be 8 bytes long, but tried to decrypt one {0} bytes long" | |||||
| raise BlockSizeError(e.format(len(data))) | |||||
| # Use big endianess since that's what everyone else uses | # Use big endianess since that's what everyone else uses | ||||
| cl = ord (data[3]) | (ord (data[2]) << 8) | (ord (data[1]) << 16) | (ord (data[0]) << 24) | cl = ord (data[3]) | (ord (data[2]) << 8) | (ord (data[1]) << 16) | (ord (data[0]) << 24) | ||||
| @@ -482,16 +482,18 @@ class Blowfish(object): | |||||
| return 56 * 8 | return 56 * 8 | ||||
| def verify_key(self, key): | def verify_key(self, key): | ||||
| """Make sure our key is not too short or too long; if there's a | |||||
| problem, raise KeyTooShortError() or KeyTooLongError().""" | |||||
| """Make sure our key is not too short or too long. | |||||
| If there's a problem, raise KeyTooShortError() or KeyTooLongError(). | |||||
| """ | |||||
| if not key: | if not key: | ||||
| raise KeyLengthError("no key given") | raise KeyLengthError("no key given") | ||||
| if len(key) < 8: | if len(key) < 8: | ||||
| raise KeyLengthError(("key is {0} bytes long, but it must be at " + | |||||
| "least 8").format(len(key))) | |||||
| e = "key is {0} bytes long, but it must be at least 8" | |||||
| raise KeyLengthError(e.format(len(key))) | |||||
| if len(key) > 56: | if len(key) > 56: | ||||
| raise KeyLengthError(("key is {0} bytes long, but it must be " + | |||||
| "less than 56").format(len(key))) | |||||
| e = "key is {0} bytes long, but it must be less than 56" | |||||
| raise KeyLengthError(e.format(len(key))) | |||||
| def encrypt(key, plaintext): | def encrypt(key, plaintext): | ||||
| """Encrypt any length of plaintext using a given key that must be between | """Encrypt any length of plaintext using a given key that must be between | ||||
| @@ -518,24 +520,25 @@ def decrypt(key, cyphertext): | |||||
| try: | try: | ||||
| cyphertext = cyphertext.decode("hex") | cyphertext = cyphertext.decode("hex") | ||||
| except TypeError as error: | |||||
| except (TypeError, AttributeError) as error: | |||||
| e = error.message | e = error.message | ||||
| raise DecryptionError("cyphertext could not be decoded: " + e.lower()) | raise DecryptionError("cyphertext could not be decoded: " + e.lower()) | ||||
| if len(cyphertext) % 8 > 0: | if len(cyphertext) % 8 > 0: | ||||
| raise DecryptionError("cyphertext cannot be broken into " + | |||||
| "8-byte blocks evenly") | |||||
| e = "cyphertext cannot be broken into 8-byte blocks evenly" | |||||
| raise DecryptionError(e) | |||||
| blocks = [cyphertext[f:f+8] for f in range(0, len(cyphertext), 8)] | blocks = [cyphertext[f:f+8] for f in range(0, len(cyphertext), 8)] | ||||
| msg = ''.join(map(cypher.decrypt, blocks)) | msg = ''.join(map(cypher.decrypt, blocks)) | ||||
| if not msg.startswith("TRUE"): # sanity check to ensure valid decryption | |||||
| raise DecryptionError("the given key is incorrect, or part of the " + | |||||
| "cyphertext is malformed") | |||||
| # Sanity check to ensure valid decryption: | |||||
| if not msg.startswith("TRUE"): | |||||
| e = "the given key is incorrect, or part of the cyphertext is malformed" | |||||
| raise DecryptionError(e) | |||||
| size, msg = msg[4:].split("|", 1) | size, msg = msg[4:].split("|", 1) | ||||
| while len(msg) > int(size): | while len(msg) > int(size): | ||||
| msg = msg[:-1] # remove the padding that we applied earlier | |||||
| msg = msg[:-1] # Remove the padding that we applied earlier | |||||
| return msg | return msg | ||||
| @@ -4,6 +4,7 @@ import platform | |||||
| import time | import time | ||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| import config | |||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||
| """Not an actual command, this module is used to respond to the CTCP | """Not an actual command, this module is used to respond to the CTCP | ||||
| @@ -40,6 +41,7 @@ class Command(BaseCommand): | |||||
| self.connection.notice(target, "\x01TIME {0}\x01".format(ts)) | self.connection.notice(target, "\x01TIME {0}\x01".format(ts)) | ||||
| elif command == "VERSION": | elif command == "VERSION": | ||||
| vers = "EarwigBot - 0.1-dev - Python/{0} https://github.com/earwig/earwigbot" | |||||
| vers = vers.format(platform.python_version()) | |||||
| default = "EarwigBot - 0.1-dev - Python/$1 https://github.com/earwig/earwigbot" | |||||
| vers = config.metadata.get("ircVersion", default) | |||||
| vers = vers.replace("$1", platform.python_version()) | |||||
| self.connection.notice(target, "\x01VERSION {0}\x01".format(vers)) | self.connection.notice(target, "\x01VERSION {0}\x01".format(vers)) | ||||
| @@ -8,13 +8,19 @@ including encrypting and decrypting passwords and making a new config file from | |||||
| scratch at the inital bot run. | scratch at the inital bot run. | ||||
| Usually you'll just want to do "from core import config" and access config data | Usually you'll just want to do "from core import config" and access config data | ||||
| from within config's three global variables and one function: | |||||
| * config.components - a list of enabled components | |||||
| * config.wiki - a dict of config information for wiki-editing | |||||
| * config.irc - a dict of config information for IRC | |||||
| * config.schedule() - returns a list of tasks scheduled to run at a given | |||||
| time | |||||
| from within config's four global variables and one function: | |||||
| * config.components - a list of enabled components | |||||
| * config.wiki - a dict of information about wiki-editing | |||||
| * config.irc - a dict of information about IRC | |||||
| * config.metadata - a dict of miscellaneous information | |||||
| * config.schedule() - returns a list of tasks scheduled to run at a given time | |||||
| Additionally, functions related to config loading: | |||||
| * config.load() - loads and parses our config file, returning True if | |||||
| passwords are stored encrypted or False otherwise | |||||
| * config.decrypt() - given a key, decrypts passwords inside our config | |||||
| variables; won't work if passwords aren't encrypted | |||||
| """ | """ | ||||
| import json | import json | ||||
| @@ -26,19 +32,12 @@ script_dir = path.dirname(path.abspath(__file__)) | |||||
| root_dir = path.split(script_dir)[0] | root_dir = path.split(script_dir)[0] | ||||
| config_path = path.join(root_dir, "config.json") | config_path = path.join(root_dir, "config.json") | ||||
| _config = None # holds data loaded from our config file | |||||
| # set our three easy-config-access global variables to None | |||||
| components, wiki, irc = (None, None, None) | |||||
| _config = None # Holds data loaded from our config file | |||||
| def is_config_loaded(): | |||||
| """Return True if our config file has already been loaded, and False if it | |||||
| hasn't.""" | |||||
| if _config is not None: | |||||
| return True | |||||
| return False | |||||
| # Set our four easy-config-access global variables to None | |||||
| components, wiki, irc, metadata = None, None, None, None | |||||
| def load_config(): | |||||
| def _load(): | |||||
| """Load data from our JSON config file (config.json) into _config.""" | """Load data from our JSON config file (config.json) into _config.""" | ||||
| global _config | global _config | ||||
| with open(config_path, 'r') as fp: | with open(config_path, 'r') as fp: | ||||
| @@ -49,86 +48,92 @@ def load_config(): | |||||
| print error | print error | ||||
| exit(1) | exit(1) | ||||
| def verify_config(): | |||||
| """Check to see if we have a valid config file, and if not, notify the | |||||
| user. If there is no config file at all, offer to make one; otherwise, | |||||
| exit. If everything goes well, return True if stored passwords are | |||||
| encrypted in the file, or False if they are not.""" | |||||
| if path.exists(config_path): | |||||
| load_config() | |||||
| try: | |||||
| return _config["encryptPasswords"] # are passwords encrypted? | |||||
| except KeyError: | |||||
| return False # assume passwords are not encrypted by default | |||||
| def _make_new(): | |||||
| """Make a new config file based on the user's input.""" | |||||
| encrypt = raw_input("Would you like to encrypt passwords stored in config.json? [y/n] ") | |||||
| if encrypt.lower().startswith("y"): | |||||
| is_encrypted = True | |||||
| else: | else: | ||||
| is_encrypted = False | |||||
| return is_encrypted | |||||
| def is_loaded(): | |||||
| """Return True if our config file has been loaded, otherwise False.""" | |||||
| return _config is not None | |||||
| def load(): | |||||
| """Load, or reload, our config file. | |||||
| First, check if we have a valid config file, and if not, notify the user. | |||||
| If there is no config file at all, offer to make one, otherwise exit. | |||||
| Store data from our config file in four global variables (components, wiki, | |||||
| irc, metadata) for easy access (as well as the internal _config variable). | |||||
| If everything goes well, return True if stored passwords are | |||||
| encrypted in the file, or False if they are not. | |||||
| """ | |||||
| global components, wiki, irc, metadata | |||||
| if not path.exists(config_path): | |||||
| print "You haven't configured the bot yet!" | print "You haven't configured the bot yet!" | ||||
| choice = raw_input("Would you like to do this now? [y/n] ") | choice = raw_input("Would you like to do this now? [y/n] ") | ||||
| if choice.lower().startswith("y"): | if choice.lower().startswith("y"): | ||||
| return make_new_config() | |||||
| return _make_new() | |||||
| else: | else: | ||||
| exit(1) | exit(1) | ||||
| def parse_config(key): | |||||
| """Store data from our config file in three global variables for easy | |||||
| access, and use the key to unencrypt passwords. Catch password decryption | |||||
| errors and report them to the user.""" | |||||
| global components, wiki, irc | |||||
| _load() | |||||
| load_config() # we might be re-loading unnecessarily here, but no harm in | |||||
| # that! | |||||
| try: | |||||
| components = _config["components"] | |||||
| except KeyError: | |||||
| components = [] | |||||
| try: | |||||
| wiki = _config["wiki"] | |||||
| except KeyError: | |||||
| wiki = {} | |||||
| try: | |||||
| irc = _config["irc"] | |||||
| except KeyError: | |||||
| irc = {} | |||||
| components = _config.get("components", []) | |||||
| wiki = _config.get("wiki", {}) | |||||
| irc = _config.get("irc", {}) | |||||
| metadata = _config.get("metadata", {}) | |||||
| # Are passwords encrypted? | |||||
| return metadata.get("encryptPasswords", False) | |||||
| def decrypt(key): | |||||
| """Use the key to decrypt passwords in our config file. | |||||
| Call this if load() returns True. Catch password decryption errors and | |||||
| report them to the user. | |||||
| """ | |||||
| global irc, wiki | |||||
| try: | try: | ||||
| try: | |||||
| if _config["encryptPasswords"]: | |||||
| decrypt(key, "wiki['password']") | |||||
| decrypt(key, "irc['frontend']['nickservPassword']") | |||||
| decrypt(key, "irc['watcher']['nickservPassword']") | |||||
| except KeyError: | |||||
| pass | |||||
| item = wiki.get("password") | |||||
| if item: | |||||
| wiki["password"] = blowfish.decrypt(key, item) | |||||
| item = irc.get("frontend").get("nickservPassword") | |||||
| if item: | |||||
| irc["frontend"]["nickservPassword"] = blowfish.decrypt(key, item) | |||||
| item = irc.get("watcher").get("nickservPassword") | |||||
| if item: | |||||
| irc["watcher"]["nickservPassword"] = blowfish.decrypt(key, item) | |||||
| except blowfish.BlowfishError as error: | except blowfish.BlowfishError as error: | ||||
| print "\nError decrypting passwords:" | print "\nError decrypting passwords:" | ||||
| print "{0}: {1}.".format(error.__class__.__name__, error) | print "{0}: {1}.".format(error.__class__.__name__, error) | ||||
| exit(1) | exit(1) | ||||
| def decrypt(key, item): | |||||
| """Decrypt 'item' with blowfish.decrypt() using the given key and set it to | |||||
| the decrypted result. 'item' should be a string, like | |||||
| decrypt(key, "wiki['password']"), NOT decrypt(key, wiki['password'), | |||||
| because that won't work.""" | |||||
| global irc, wiki | |||||
| try: | |||||
| result = blowfish.decrypt(key, eval(item)) | |||||
| except KeyError: | |||||
| return | |||||
| exec "{0} = result".format(item) | |||||
| def schedule(minute, hour, month_day, month, week_day): | def schedule(minute, hour, month_day, month, week_day): | ||||
| """Return a list of tasks that are scheduled to run at the time specified | |||||
| by the function arguments. The schedule data comes from our config file's | |||||
| 'schedule' field, which is stored as _config["schedule"]. Call this | |||||
| function with config.schedule(args).""" | |||||
| tasks = [] # tasks to run this turn, each as a tuple of either (task_name, | |||||
| # kwargs), or just task_name | |||||
| """Return a list of tasks scheduled to run at the specified time. | |||||
| The schedule data comes from our config file's 'schedule' field, which is | |||||
| stored as _config["schedule"]. Call this function as config.schedule(args). | |||||
| """ | |||||
| # Tasks to run this turn, each as a list of either [task_name, kwargs], or | |||||
| # just the task_name: | |||||
| tasks = [] | |||||
| now = {"minute": minute, "hour": hour, "month_day": month_day, | now = {"minute": minute, "hour": hour, "month_day": month_day, | ||||
| "month": month, "week_day": week_day} | "month": month, "week_day": week_day} | ||||
| try: | |||||
| data = _config["schedule"] | |||||
| except KeyError: | |||||
| return [] # nothing is in our schedule | |||||
| data = _config.get("schedule", []) | |||||
| for event in data: | for event in data: | ||||
| do = True | do = True | ||||
| for key, value in now.items(): | for key, value in now.items(): | ||||
| @@ -146,15 +151,3 @@ def schedule(minute, hour, month_day, month, week_day): | |||||
| pass | pass | ||||
| return tasks | return tasks | ||||
| def make_new_config(): | |||||
| """Make a new config file based on the user's input.""" | |||||
| encrypt = raw_input("Would you like to encrypt passwords stored in " + | |||||
| "config.json? [y/n] ") | |||||
| if encrypt.lower().startswith("y"): | |||||
| is_encrypted = True | |||||
| else: | |||||
| is_encrypted = False | |||||
| return is_encrypted | |||||
| @@ -4,9 +4,9 @@ | |||||
| """ | """ | ||||
| EarwigBot's Core | EarwigBot's Core | ||||
| This (should) not be run directly; the wrapper in "earwigbot.py" is preferred, | |||||
| This should not be run directly; the wrapper in "earwigbot.py" is preferred, | |||||
| but it should work fine alone, as long as you enter the password-unlock key at | but it should work fine alone, as long as you enter the password-unlock key at | ||||
| the initial hidden prompt. | |||||
| the initial hidden prompt if one is needed. | |||||
| The core is essentially responsible for starting the various bot components | The core is essentially responsible for starting the various bot components | ||||
| (irc, scheduler, etc) and making sure they are all happy. An explanation of the | (irc, scheduler, etc) and making sure they are all happy. An explanation of the | ||||
| @@ -103,12 +103,14 @@ def irc_frontend(): | |||||
| f_conn.close() | f_conn.close() | ||||
| def run(): | def run(): | ||||
| config.load() | |||||
| try: | try: | ||||
| key = raw_input() # wait for our password unlock key from the bot's | |||||
| except EOFError: # wrapper | |||||
| key = None | |||||
| config.parse_config(key) # load data from the config file and parse it | |||||
| # using the unlock key | |||||
| key = raw_input() # wait for our password decrypt key from the bot's | |||||
| except EOFError: # wrapper, then decrypt passwords | |||||
| pass | |||||
| else: | |||||
| config.decrypt(key) | |||||
| enabled = config.components | enabled = config.components | ||||
| if "irc_frontend" in enabled: # make the frontend run on our primary | if "irc_frontend" in enabled: # make the frontend run on our primary | ||||
| @@ -120,7 +122,7 @@ def run(): | |||||
| tasks.load() # watcher on another thread iff it | tasks.load() # watcher on another thread iff it | ||||
| if "irc_watcher" in enabled: # is enabled | if "irc_watcher" in enabled: # is enabled | ||||
| print "\nStarting IRC watcher..." | print "\nStarting IRC watcher..." | ||||
| t_watcher = threading.Thread(target=irc_watcher, args=()) | |||||
| t_watcher = threading.Thread(target=irc_watcher) | |||||
| t_watcher.name = "irc-watcher" | t_watcher.name = "irc-watcher" | ||||
| t_watcher.daemon = True | t_watcher.daemon = True | ||||
| t_watcher.start() | t_watcher.start() | ||||
| @@ -3,18 +3,18 @@ | |||||
| """ | """ | ||||
| EarwigBot's Wiki Toolset: Constants | EarwigBot's Wiki Toolset: Constants | ||||
| This module defines some useful constants, such as default namespace IDs for | |||||
| easy lookup and our user agent. | |||||
| This module defines some useful constants: | |||||
| * USER_AGENT - our default User Agent when making API queries | |||||
| * NS_* - default namespace IDs for easy lookup | |||||
| Import with `from wiki.constants import *`. | |||||
| Import with `from wiki import constants` or `from wiki.constants import *`. | |||||
| """ | """ | ||||
| # Default User Agent when making API queries: | |||||
| import platform | import platform | ||||
| # User agent when making API queries | |||||
| USER_AGENT = "EarwigBot/0.1-dev (Python/{0}; https://github.com/earwig/earwigbot)".format(platform.python_version()) | USER_AGENT = "EarwigBot/0.1-dev (Python/{0}; https://github.com/earwig/earwigbot)".format(platform.python_version()) | ||||
| # Default namespace IDs | |||||
| # Default namespace IDs: | |||||
| NS_MAIN = 0 | NS_MAIN = 0 | ||||
| NS_TALK = 1 | NS_TALK = 1 | ||||
| NS_USER = 2 | NS_USER = 2 | ||||
| @@ -14,6 +14,7 @@ from cookielib import LWPCookieJar, LoadError | |||||
| import errno | import errno | ||||
| from getpass import getpass | from getpass import getpass | ||||
| from os import chmod, path | from os import chmod, path | ||||
| import platform | |||||
| import stat | import stat | ||||
| import config | import config | ||||
| @@ -30,12 +31,10 @@ def _load_config(): | |||||
| directly from Python's interpreter and not the bot itself, because | directly from Python's interpreter and not the bot itself, because | ||||
| earwigbot.py or core/main.py will already call these functions. | earwigbot.py or core/main.py will already call these functions. | ||||
| """ | """ | ||||
| is_encrypted = config.verify_config() | |||||
| is_encrypted = config.load() | |||||
| if is_encrypted: # passwords in the config file are encrypted | if is_encrypted: # passwords in the config file are encrypted | ||||
| key = getpass("Enter key to unencrypt bot passwords: ") | key = getpass("Enter key to unencrypt bot passwords: ") | ||||
| config.parse_config(key) | |||||
| else: | |||||
| config.parse_config(None) | |||||
| config.decrypt(key) | |||||
| def _get_cookiejar(): | def _get_cookiejar(): | ||||
| """Returns a LWPCookieJar object loaded from our .cookies file. The same | """Returns a LWPCookieJar object loaded from our .cookies file. The same | ||||
| @@ -87,6 +86,10 @@ def _get_site_object_from_dict(name, d): | |||||
| namespaces = d.get("namespaces", {}) | namespaces = d.get("namespaces", {}) | ||||
| login = (config.wiki.get("username"), config.wiki.get("password")) | login = (config.wiki.get("username"), config.wiki.get("password")) | ||||
| cookiejar = _get_cookiejar() | cookiejar = _get_cookiejar() | ||||
| user_agent = config.metadata.get("userAgent") | |||||
| if user_agent: | |||||
| user_agent = user_agent.replace("$1", platform.python_version()) | |||||
| for key, value in namespaces.items(): # Convert string keys to integers | for key, value in namespaces.items(): # Convert string keys to integers | ||||
| del namespaces[key] | del namespaces[key] | ||||
| @@ -98,7 +101,8 @@ def _get_site_object_from_dict(name, d): | |||||
| return Site(name=name, project=project, lang=lang, base_url=base_url, | return Site(name=name, project=project, lang=lang, base_url=base_url, | ||||
| article_path=article_path, script_path=script_path, sql=sql, | article_path=article_path, script_path=script_path, sql=sql, | ||||
| namespaces=namespaces, login=login, cookiejar=cookiejar) | |||||
| namespaces=namespaces, login=login, cookiejar=cookiejar, | |||||
| user_agent=user_agent) | |||||
| def get_site(name=None, project=None, lang=None): | def get_site(name=None, project=None, lang=None): | ||||
| """Returns a Site instance based on information from our config file. | """Returns a Site instance based on information from our config file. | ||||
| @@ -40,7 +40,8 @@ class Site(object): | |||||
| def __init__(self, name=None, project=None, lang=None, base_url=None, | def __init__(self, name=None, project=None, lang=None, base_url=None, | ||||
| article_path=None, script_path=None, sql=(None, None), | article_path=None, script_path=None, sql=(None, None), | ||||
| namespaces=None, login=(None, None), cookiejar=None): | |||||
| namespaces=None, login=(None, None), cookiejar=None, | |||||
| user_agent=None): | |||||
| """Constructor for new Site instances. | """Constructor for new Site instances. | ||||
| This probably isn't necessary to call yourself unless you're building a | This probably isn't necessary to call yourself unless you're building a | ||||
| @@ -57,8 +58,8 @@ class Site(object): | |||||
| the API, and then log in if a username/pass was given and we aren't | the API, and then log in if a username/pass was given and we aren't | ||||
| already logged in. | already logged in. | ||||
| """ | """ | ||||
| # attributes referring to site information, filled in by an API query | |||||
| # if they are missing (and an API url can be determined) | |||||
| # Attributes referring to site information, filled in by an API query | |||||
| # if they are missing (and an API url can be determined): | |||||
| self._name = name | self._name = name | ||||
| self._project = project | self._project = project | ||||
| self._lang = lang | self._lang = lang | ||||
| @@ -68,19 +69,21 @@ class Site(object): | |||||
| self._sql = sql | self._sql = sql | ||||
| self._namespaces = namespaces | self._namespaces = namespaces | ||||
| # set up cookiejar and URL opener for making API queries | |||||
| # Set up cookiejar and URL opener for making API queries: | |||||
| if cookiejar is not None: | if cookiejar is not None: | ||||
| self._cookiejar = cookiejar | self._cookiejar = cookiejar | ||||
| else: | else: | ||||
| self._cookiejar = CookieJar() | self._cookiejar = CookieJar() | ||||
| if user_agent is None: | |||||
| user_agent = USER_AGENT # Set default UA from wiki.constants | |||||
| self._opener = build_opener(HTTPCookieProcessor(self._cookiejar)) | self._opener = build_opener(HTTPCookieProcessor(self._cookiejar)) | ||||
| self._opener.addheaders = [("User-Agent", USER_AGENT), | |||||
| self._opener.addheaders = [("User-Agent", user_agent), | |||||
| ("Accept-Encoding", "gzip")] | ("Accept-Encoding", "gzip")] | ||||
| # get all of the above attributes that were not specified as arguments | |||||
| # Get all of the above attributes that were not specified as arguments: | |||||
| self._load_attributes() | self._load_attributes() | ||||
| # if we have a name/pass and the API says we're not logged in, log in | |||||
| # If we have a name/pass and the API says we're not logged in, log in: | |||||
| self._login_info = name, password = login | self._login_info = name, password = login | ||||
| if name is not None and password is not None: | if name is not None and password is not None: | ||||
| logged_in_as = self._get_username_from_cookies() | logged_in_as = self._get_username_from_cookies() | ||||
| @@ -112,7 +115,7 @@ class Site(object): | |||||
| raise SiteAPIError(e) | raise SiteAPIError(e) | ||||
| url = ''.join((self._base_url, self._script_path, "/api.php")) | url = ''.join((self._base_url, self._script_path, "/api.php")) | ||||
| params["format"] = "json" # this is the only format we understand | |||||
| params["format"] = "json" # This is the only format we understand | |||||
| data = urlencode(params) | data = urlencode(params) | ||||
| print url, data # debug code | print url, data # debug code | ||||
| @@ -135,7 +138,7 @@ class Site(object): | |||||
| stream = StringIO(result) | stream = StringIO(result) | ||||
| gzipper = GzipFile(fileobj=stream) | gzipper = GzipFile(fileobj=stream) | ||||
| result = gzipper.read() | result = gzipper.read() | ||||
| return loads(result) # parse as a JSON object | |||||
| return loads(result) # Parse as a JSON object | |||||
| def _load_attributes(self, force=False): | def _load_attributes(self, force=False): | ||||
| """Load data about our Site from the API. | """Load data about our Site from the API. | ||||
| @@ -147,8 +150,8 @@ class Site(object): | |||||
| Additionally, you can call this with `force=True` to forcibly reload | Additionally, you can call this with `force=True` to forcibly reload | ||||
| all attributes. | all attributes. | ||||
| """ | """ | ||||
| # all attributes to be loaded, except _namespaces, which is a special | |||||
| # case because it requires additional params in the API query | |||||
| # All attributes to be loaded, except _namespaces, which is a special | |||||
| # case because it requires additional params in the API query: | |||||
| attrs = [self._name, self._project, self._lang, self._base_url, | attrs = [self._name, self._project, self._lang, self._base_url, | ||||
| self._article_path, self._script_path] | self._article_path, self._script_path] | ||||
| @@ -158,9 +161,9 @@ class Site(object): | |||||
| params["siprop"] = "general|namespaces|namespacealiases" | params["siprop"] = "general|namespaces|namespacealiases" | ||||
| result = self._api_query(params) | result = self._api_query(params) | ||||
| self._load_namespaces(result) | self._load_namespaces(result) | ||||
| elif all(attrs): # everything is already specified and we're not told | |||||
| elif all(attrs): # Everything is already specified and we're not told | |||||
| return # to force a reload, so do nothing | return # to force a reload, so do nothing | ||||
| else: # we're only loading attributes other than _namespaces | |||||
| else: # We're only loading attributes other than _namespaces | |||||
| params["siprop"] = "general" | params["siprop"] = "general" | ||||
| result = self._api_query(params) | result = self._api_query(params) | ||||
| @@ -240,9 +243,9 @@ class Site(object): | |||||
| continue | continue | ||||
| if cookie.name != name: | if cookie.name != name: | ||||
| continue | continue | ||||
| # build a regex that will match domains this cookie affects | |||||
| # Build a regex that will match domains this cookie affects: | |||||
| search = ''.join(("(.*?)", re_escape(cookie.domain))) | search = ''.join(("(.*?)", re_escape(cookie.domain))) | ||||
| if re_match(search, domain): # test it against our site | |||||
| if re_match(search, domain): # Test it against our site | |||||
| user_name = self._get_cookie("centralauth_User", cookie.domain) | user_name = self._get_cookie("centralauth_User", cookie.domain) | ||||
| if user_name is not None: | if user_name is not None: | ||||
| return user_name.value | return user_name.value | ||||
| @@ -402,7 +405,7 @@ class Site(object): | |||||
| """ | """ | ||||
| lname = name.lower() | lname = name.lower() | ||||
| for ns_id, names in self._namespaces.items(): | for ns_id, names in self._namespaces.items(): | ||||
| lnames = [n.lower() for n in names] # be case-insensitive | |||||
| lnames = [n.lower() for n in names] # Be case-insensitive | |||||
| if lname in lnames: | if lname in lnames: | ||||
| return ns_id | return ns_id | ||||
| @@ -421,7 +424,7 @@ class Site(object): | |||||
| """ | """ | ||||
| prefixes = self.namespace_id_to_name(NS_CATEGORY, all=True) | prefixes = self.namespace_id_to_name(NS_CATEGORY, all=True) | ||||
| prefix = title.split(":", 1)[0] | prefix = title.split(":", 1)[0] | ||||
| if prefix != title: # avoid a page that is simply "Category" | |||||
| if prefix != title: # Avoid a page that is simply "Category" | |||||
| if prefix in prefixes: | if prefix in prefixes: | ||||
| return Category(self, title, follow_redirects) | return Category(self, title, follow_redirects) | ||||
| return Page(self, title, follow_redirects) | return Page(self, title, follow_redirects) | ||||
| @@ -33,7 +33,7 @@ bot_script = path.join(path.dirname(path.abspath(__file__)), "bot", "main.py") | |||||
| def main(): | def main(): | ||||
| print "EarwigBot v{0}\n".format(__version__) | print "EarwigBot v{0}\n".format(__version__) | ||||
| is_encrypted = config.verify_config() | |||||
| is_encrypted = config.load() | |||||
| if is_encrypted: # passwords in the config file are encrypted | if is_encrypted: # passwords in the config file are encrypted | ||||
| key = getpass("Enter key to unencrypt bot passwords: ") | key = getpass("Enter key to unencrypt bot passwords: ") | ||||
| else: | else: | ||||
| @@ -41,7 +41,7 @@ def main(): | |||||
| while 1: | while 1: | ||||
| bot = Popen([executable, bot_script], stdin=PIPE) | bot = Popen([executable, bot_script], stdin=PIPE) | ||||
| bot.communicate(key) # give the key to core.config.load_config() | |||||
| bot.communicate(key) # give the key to core.config.decrypt() | |||||
| return_code = bot.wait() | return_code = bot.wait() | ||||
| if return_code == 1: | if return_code == 1: | ||||
| exit() # let critical exceptions in the subprocess cause us to | exit() # let critical exceptions in the subprocess cause us to | ||||