diff --git a/earwigbot/commands/crypt.py b/earwigbot/commands/crypt.py index 0123be1..92f458c 100644 --- a/earwigbot/commands/crypt.py +++ b/earwigbot/commands/crypt.py @@ -24,8 +24,11 @@ import hashlib from Crypto.Cipher import Blowfish +from earwigbot import importer from earwigbot.commands import Command +Blowfish = importer.new("Crypto.Cipher.Blowfish") + class Crypt(Command): """Provides hash functions with !hash (!hash list for supported algorithms) and Blowfish encryption with !encrypt and !decrypt.""" @@ -66,7 +69,13 @@ class Crypt(Command): self.reply(data, msg.format(data.command)) return - cipher = Blowfish.new(hashlib.sha256(key).digest()) + try: + cipher = Blowfish.new(hashlib.sha256(key).digest()) + except ImportError: + msg = "This command requires the 'pycrypto' package: https://www.dlitz.net/software/pycrypto/" + self.reply(data, msg) + return + try: if data.command == "encrypt": if len(text) % 8: diff --git a/earwigbot/commands/time_command.py b/earwigbot/commands/time_command.py index f110d1a..b0dc657 100644 --- a/earwigbot/commands/time_command.py +++ b/earwigbot/commands/time_command.py @@ -56,7 +56,7 @@ class Time(Command): try: tzinfo = pytz.timezone(timezone) except ImportError: - msg = "This command requires the 'pytz' module: http://pytz.sourceforge.net/" + msg = "This command requires the 'pytz' package: http://pytz.sourceforge.net/" self.reply(data, msg) return except pytz.exceptions.UnknownTimeZoneError: diff --git a/earwigbot/config/__init__.py b/earwigbot/config/__init__.py index b0d7fae..911a496 100644 --- a/earwigbot/config/__init__.py +++ b/earwigbot/config/__init__.py @@ -28,10 +28,9 @@ import logging.handlers from os import mkdir, path import stat -from Crypto.Cipher import Blowfish -import bcrypt import yaml +from earwigbot import importer from earwigbot.config.formatter import BotFormatter from earwigbot.config.node import ConfigNode from earwigbot.config.ordered_yaml import OrderedLoader @@ -39,6 +38,9 @@ from earwigbot.config.permissions import PermissionsDB from earwigbot.config.script import ConfigScript from earwigbot.exceptions import NoConfigError +Blowfish = importer.new("Crypto.Cipher.Blowfish") +bcrypt = importer.new("bcrypt") + __all__ = ["BotConfig"] class BotConfig(object): diff --git a/earwigbot/config/script.py b/earwigbot/config/script.py index 9d953e9..0b35aed 100644 --- a/earwigbot/config/script.py +++ b/earwigbot/config/script.py @@ -29,13 +29,14 @@ import stat import sys from textwrap import fill, wrap -from Crypto.Cipher import Blowfish -import bcrypt import yaml -from earwigbot import exceptions +from earwigbot import exceptions, importer from earwigbot.config.ordered_yaml import OrderedDumper +Blowfish = importer.new("Crypto.Cipher.Blowfish") +bcrypt = importer.new("bcrypt") + __all__ = ["ConfigScript"] RULES_TEMPLATE = """# -*- coding: utf-8 -*- @@ -145,17 +146,30 @@ class ConfigScript(object): is to run on a public computer like the Toolserver, but otherwise the need to enter a key everytime you start the bot may be annoying.""") + self.data["metadata"]["encryptPasswords"] = False if self._ask_bool("Encrypt stored passwords?"): - self.data["metadata"]["encryptPasswords"] = True key = getpass(self.PROMPT + "Enter an encryption key: ") msg = "Running {0} rounds of bcrypt...".format(self.BCRYPT_ROUNDS) self._print_no_nl(msg) - signature = bcrypt.hashpw(key, bcrypt.gensalt(self.BCRYPT_ROUNDS)) - self.data["metadata"]["signature"] = signature - self._cipher = Blowfish.new(sha256(key).digest()) - print " done." - else: - self.data["metadata"]["encryptPasswords"] = False + try: + salt = bcrypt.gensalt(self.BCRYPT_ROUNDS) + signature = bcrypt.hashpw(key, salt) + self._cipher = Blowfish.new(sha256(key).digest()) + except ImportError: + print " error!" + self._print("""Encryption requires the 'py-bcrypt' and + 'pycrypto' packages:""") + strt, end = " * \x1b[36m", "\x1b[0m" + print strt + "http://www.mindrot.org/projects/py-bcrypt/" + end + print strt + "https://www.dlitz.net/software/pycrypto/" + end + self._print("""I will disable encryption for now; restart + configuration after installing these packages if + you want it.""") + self._pause() + else: + self.data["metadata"]["encryptPasswords"] = True + self.data["metadata"]["signature"] = signature + print " done." print self._print("""The bot can temporarily store its logs in the logs/ diff --git a/earwigbot/wiki/copyvios/__init__.py b/earwigbot/wiki/copyvios/__init__.py index 425d99a..59e0dcb 100644 --- a/earwigbot/wiki/copyvios/__init__.py +++ b/earwigbot/wiki/copyvios/__init__.py @@ -95,8 +95,8 @@ class CopyvioMixIn(object): if engine == "Yahoo! BOSS": try: oauth.__version__ # Force-load the lazy module - except (ImportError, AttributeError): - e = "The package 'oauth2' could not be imported" + except ImportError: + e = "Yahoo! BOSS requires the 'oauth2' package: https://github.com/simplegeo/python-oauth2" raise exceptions.UnsupportedSearchEngineError(e) return YahooBOSSSearchEngine(credentials) diff --git a/earwigbot/wiki/site.py b/earwigbot/wiki/site.py index 765ea81..0d0cb0c 100644 --- a/earwigbot/wiki/site.py +++ b/earwigbot/wiki/site.py @@ -530,7 +530,7 @@ class Site(object): try: self._sql_conn = oursql.connect(**args) except ImportError: - e = "Module 'oursql' is required for SQL queries." + e = "SQL querying requires the 'oursql' package: http://packages.python.org/oursql/" raise exceptions.SQLError(e) def _get_service_order(self): diff --git a/setup.py b/setup.py index 3bbc638..d2cddb0 100644 --- a/setup.py +++ b/setup.py @@ -25,24 +25,32 @@ from setuptools import setup, find_packages from earwigbot import __version__ -# Not all of these dependencies are required, particularly the copyvio-specific -# ones (bs4, lxml, nltk, and oauth2) and the command-specific one (pytz). The -# bot should run fine without them, but will raise an exception if you try to -# detect copyvios or run a command that requries one. - -dependencies = [ +required_deps = [ "PyYAML >= 3.10", # Parsing config files - "beautifulsoup4 >= 4.1.1", # Parsing/scraping HTML for copyvios - "lxml >= 2.3.5", # Faster parser for BeautifulSoup "mwparserfromhell >= 0.1", # Parsing wikicode for manipulation - "nltk >= 2.0.2", # Parsing sentences to split article content for copyvios - "oauth2 >= 1.5.211", # Interfacing with Yahoo! BOSS Search for copyvios - "oursql >= 0.9.3.1", # Interfacing with MediaWiki databases - "py-bcrypt >= 0.2", # Hashing the bot key in the config file - "pycrypto >= 2.6", # Storing bot passwords and keys in the config file - "pytz >= 2012d", # Handling timezones for the !time IRC command ] +extra_deps = { + "crypto": [ + "py-bcrypt >= 0.2", # Hashing the bot key in the config file + "pycrypto >= 2.6", # Storing bot passwords and keys in the config file + ], + "sql": [ + "oursql >= 0.9.3.1", # Interfacing with MediaWiki databases + ], + "copyvios": [ + "beautifulsoup4 >= 4.1.1", # Parsing/scraping HTML + "lxml >= 2.3.5", # Faster parser for BeautifulSoup + "nltk >= 2.0.2", # Parsing sentences to split article content + "oauth2 >= 1.5.211", # Interfacing with Yahoo! BOSS Search + ], + "time": [ + "pytz >= 2012d", # Handling timezones for the !time IRC command + ], +} + +dependencies = required_deps + sum(extra_deps.values(), []) + with open("README.rst") as fp: long_docs = fp.read()