Browse Source

Miscellaneous tweaks and fixes.

tags/v0.1^2
Ben Kurtovic 12 years ago
parent
commit
a3b64a38d9
3 changed files with 58 additions and 33 deletions
  1. +3
    -0
      earwigbot/commands/crypt.py
  2. +1
    -1
      earwigbot/config/ordered_yaml.py
  3. +54
    -32
      earwigbot/config/script.py

+ 3
- 0
earwigbot/commands/crypt.py View File

@@ -69,6 +69,9 @@ class Crypt(Command):
cipher = Blowfish.new(hashlib.sha256(key).digest()) cipher = Blowfish.new(hashlib.sha256(key).digest())
try: try:
if data.command == "encrypt": if data.command == "encrypt":
if len(text) % 8:
pad = 8 - len(text) % 8
text = text.ljust(len(text) + pad, "\x00")
self.reply(data, cipher.encrypt(text).encode("hex")) self.reply(data, cipher.encrypt(text).encode("hex"))
else: else:
self.reply(data, cipher.decrypt(text.decode("hex"))) self.reply(data, cipher.decrypt(text.decode("hex")))


+ 1
- 1
earwigbot/config/ordered_yaml.py View File

@@ -75,7 +75,7 @@ class OrderedLoader(yaml.Loader):




class OrderedDumper(yaml.Dumper): class OrderedDumper(yaml.Dumper):
"""A YAML dumper that dumps mappings into ordered dictionaries."""
"""A YAML dumper that dumps ordered dictionaries into mappings."""


def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(OrderedDumper, self).__init__(*args, **kwargs) super(OrderedDumper, self).__init__(*args, **kwargs)


+ 54
- 32
earwigbot/config/script.py View File

@@ -22,12 +22,18 @@


from collections import OrderedDict from collections import OrderedDict
from getpass import getpass from getpass import getpass
from hashlib import sha256
from os import chmod, mkdir, path from os import chmod, mkdir, path
import re import re
import stat import stat
from textwrap import fill, wrap from textwrap import fill, wrap


try: try:
from Crypto.Cipher import Blowfish
except ImportError:
Blowfish = None

try:
import bcrypt import bcrypt
except ImportError: except ImportError:
bcrypt = None bcrypt = None
@@ -42,7 +48,7 @@ from earwigbot.config.ordered_yaml import OrderedDumper


__all__ = ["ConfigScript"] __all__ = ["ConfigScript"]


RULES_TEMPLATE = """"# -*- coding: utf-8 -*-
RULES_TEMPLATE = """# -*- coding: utf-8 -*-


def process(bot, rc): def process(bot, rc):
\"\"\"Given a Bot() object and an RC() object, return a list of channels \"\"\"Given a Bot() object and an RC() object, return a list of channels
@@ -58,7 +64,7 @@ class ConfigScript(object):


def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.data = OrderedDict(
self.data = OrderedDict([
("metadata", OrderedDict()), ("metadata", OrderedDict()),
("components", OrderedDict()), ("components", OrderedDict()),
("wiki", OrderedDict()), ("wiki", OrderedDict()),
@@ -66,11 +72,12 @@ class ConfigScript(object):
("commands", OrderedDict()), ("commands", OrderedDict()),
("tasks", OrderedDict()), ("tasks", OrderedDict()),
("schedule", []) ("schedule", [])
)
])


self.wmf = False
self.proj = None
self.lang = None
self._cipher = None
self._wmf = False
self._proj = None
self._lang = None


def _print(self, text): def _print(self, text):
print fill(re.sub("\s\s+", " ", text), self.WIDTH) print fill(re.sub("\s\s+", " ", text), self.WIDTH)
@@ -105,6 +112,16 @@ class ConfigScript(object):
if answer.startswith("n"): if answer.startswith("n"):
return False return False


def _ask_pass(self, text):
password = getpass("> " + text + " ")
if self._cipher:
mod = len(password) % 8
if mod:
password = password.ljust(len(password) + (8 - mod), "\x00")
return self._cipher.encrypt(password).encode("hex")
else:
return password

def _ask_list(self, text): def _ask_list(self, text):
print fill(re.sub("\s\s+", " ", "> " + text), self.WIDTH) print fill(re.sub("\s\s+", " ", "> " + text), self.WIDTH)
print "[one item per line; blank line to end]:" print "[one item per line; blank line to end]:"
@@ -118,7 +135,7 @@ class ConfigScript(object):


def _set_metadata(self): def _set_metadata(self):
print print
self.data["metadata"] = OrderedDict(("version", 1))
self.data["metadata"] = OrderedDict([("version", 1)])
self._print("""I can encrypt passwords stored in your config file in self._print("""I can encrypt passwords stored in your config file in
addition to preventing other users on your system from addition to preventing other users on your system from
reading the file. Encryption is recommended is the bot reading the file. Encryption is recommended is the bot
@@ -128,9 +145,10 @@ class ConfigScript(object):
if self._ask_bool("Encrypt stored passwords?"): if self._ask_bool("Encrypt stored passwords?"):
self.data["metadata"]["encryptPasswords"] = True self.data["metadata"]["encryptPasswords"] = True
key = getpass("> Enter an encryption key: ") key = getpass("> Enter an encryption key: ")
print "Running {0} rounds of bcrypt...".format(self.BCRYPT_ROUNDS),
print "Running {0} rounds of bcrypt...".format(self.BCRYPT_ROUNDS), # STDOUT
signature = bcrypt.hashpw(key, bcrypt.gensalt(self.BCRYPT_ROUNDS)) signature = bcrypt.hashpw(key, bcrypt.gensalt(self.BCRYPT_ROUNDS))
self.data["metadata"]["signature"] = signature self.data["metadata"]["signature"] = signature
self._cipher = Blowfish.new(sha256(key).digest())
print " done." print " done."
else: else:
self.data["metadata"]["encryptPasswords"] = False self.data["metadata"]["encryptPasswords"] = False
@@ -185,7 +203,7 @@ class ConfigScript(object):
question = "Would you like to re-enter your login information?" question = "Would you like to re-enter your login information?"
if self._ask_bool(question): if self._ask_bool(question):
self.data["wiki"]["username"] = self._ask("Bot username:") self.data["wiki"]["username"] = self._ask("Bot username:")
self.data["wiki"]["password"] = getpass("> Bot password: ")
self.data["wiki"]["password"] = self._ask_pass("Bot password:")
return self._login(kwargs) return self._login(kwargs)
question = "Would you like to re-enter the site information?" question = "Would you like to re-enter the site information?"
if self._ask_bool(question): if self._ask_bool(question):
@@ -198,13 +216,13 @@ class ConfigScript(object):


def _set_wiki(self): def _set_wiki(self):
print print
self.wmf = self._ask_bool("""Will this bot run on Wikimedia Foundation
wikis, like Wikipedia?""")
if self.wmf:
self._wmf = self._ask_bool("""Will this bot run on Wikimedia Foundation
wikis, like Wikipedia?""")
if self._wmf:
msg = "Site project (e.g. 'wikipedia', 'wiktionary', 'wikimedia'):" msg = "Site project (e.g. 'wikipedia', 'wiktionary', 'wikimedia'):"
self.proj = project = self._ask(msg, default="wikipedia").lower()
self._proj = project = self._ask(msg, default="wikipedia").lower()
msg = "Site language code (e.g. 'en', 'fr', 'commons'):" msg = "Site language code (e.g. 'en', 'fr', 'commons'):"
self.lang = lang = self._ask(msg, default="en").lower()
self._lang = lang = self._ask(msg, default="en").lower()
kwargs = {"project": project, "lang": lang} kwargs = {"project": project, "lang": lang}
else: else:
msg = "Site base URL, without the script path and trailing slash;" msg = "Site base URL, without the script path and trailing slash;"
@@ -214,7 +232,7 @@ class ConfigScript(object):
kwargs = {"base_url": url, "script_path": script} kwargs = {"base_url": url, "script_path": script}


self.data["wiki"]["username"] = self._ask("Bot username:") self.data["wiki"]["username"] = self._ask("Bot username:")
self.data["wiki"]["password"] = getpass("> Bot password: ")
self.data["wiki"]["password"] = self._ask_pass("Bot password:")
self.data["wiki"]["userAgent"] = "EarwigBot/$1 (Python/$2; https://github.com/earwig/earwigbot)" self.data["wiki"]["userAgent"] = "EarwigBot/$1 (Python/$2; https://github.com/earwig/earwigbot)"
self.data["wiki"]["summary"] = "([[WP:BOT|Bot]]): $2" self.data["wiki"]["summary"] = "([[WP:BOT|Bot]]): $2"
self.data["wiki"]["useHTTPS"] = True self.data["wiki"]["useHTTPS"] = True
@@ -224,11 +242,11 @@ class ConfigScript(object):
self.data["wiki"]["defaultSite"] = self._login(kwargs).name self.data["wiki"]["defaultSite"] = self._login(kwargs).name
self.data["wiki"]["sql"] = {} self.data["wiki"]["sql"] = {}


if self.wmf:
if self._wmf:
msg = "Will this bot run from the Wikimedia Toolserver?" msg = "Will this bot run from the Wikimedia Toolserver?"
toolserver = self._ask_bool(msg, default=False) toolserver = self._ask_bool(msg, default=False)
if toolserver: if toolserver:
args = (("host", "$1-p.rrdb.toolserver.org"), ("db", "$1_p"))
args = [("host", "$1-p.rrdb.toolserver.org"), ("db", "$1_p")]
self.data["wiki"]["sql"] = OrderedDict(args) self.data["wiki"]["sql"] = OrderedDict(args)


self.data["wiki"]["shutoff"] = {} self.data["wiki"]["shutoff"] = {}
@@ -240,7 +258,7 @@ class ConfigScript(object):
separate shutoff page for each task.""") separate shutoff page for each task.""")
page = self._ask("Page title:", default="User:$1/Shutoff") page = self._ask("Page title:", default="User:$1/Shutoff")
disabled = self._ask("Page content when *not* shut off:", "run") disabled = self._ask("Page content when *not* shut off:", "run")
args = (("page", page), ("disabled", disabled))
args = [("page", page), ("disabled", disabled)]
self.data["wiki"]["shutoff"] = OrderedDict(args) self.data["wiki"]["shutoff"] = OrderedDict(args)


self.data["wiki"]["search"] = {} self.data["wiki"]["search"] = {}
@@ -258,9 +276,10 @@ class ConfigScript(object):
question = "Frontend bot's real name (gecos):" question = "Frontend bot's real name (gecos):"
frontend["realname"] = self._ask(question) frontend["realname"] = self._ask(question)
if self._ask_bool("Should the bot identify to NickServ?"): if self._ask_bool("Should the bot identify to NickServ?"):
frontend["nickservUsername"] = self._ask("NickServ username:",
frontend["nick"])
frontend["nickservPassword"] = getpass("> Nickserv password: ")
ns_user = self._ask("NickServ username:", frontend["nick"])
ns_pass = self._ask_pass("Nickserv password:")
frontend["nickservUsername"] = ns_user
frontend["nickservPassword"] = ns_pass
chan_question = "Frontend channels to join by default:" chan_question = "Frontend channels to join by default:"
frontend["channels"] = self._ask_list(chan_question) frontend["channels"] = self._ask_list(chan_question)
self._print("""The bot keeps a database of its admins (users who self._print("""The bot keeps a database of its admins (users who
@@ -283,26 +302,28 @@ class ConfigScript(object):
if self.data["components"]["irc_watcher"]: if self.data["components"]["irc_watcher"]:
print print
watcher = self.data["irc"]["watcher"] = OrderedDict() watcher = self.data["irc"]["watcher"] = OrderedDict()
if self.wmf:
if self._wmf:
watcher["host"] = "irc.wikimedia.org" watcher["host"] = "irc.wikimedia.org"
watcher["port"] = 6667 watcher["port"] = 6667
else: else:
msg = "Hostname of the watcher's IRC server, without 'irc://':" msg = "Hostname of the watcher's IRC server, without 'irc://':"
watcher["host"] = self._ask(msg) watcher["host"] = self._ask(msg)
watcher["port"] = self._ask("Watcher port:", 6667) watcher["port"] = self._ask("Watcher port:", 6667)
watcher["nick"] = self._ask("Watcher bot's nickname:",
frontend.get("nick"))
watcher["ident"] = self._ask("Watcher bot's ident:",
watcher["nick"].lower())
nick = self._ask("Watcher bot's nickname:", frontend.get("nick"))
ident = self._ask("Watcher bot's ident:", watcher["nick"].lower())
watcher["nick"] = nick
watcher["ident"] = ident
question = "Watcher bot's real name (gecos):" question = "Watcher bot's real name (gecos):"
watcher["realname"] = self._ask(question, frontend.get("realname")) watcher["realname"] = self._ask(question, frontend.get("realname"))
watcher_ns = "Should the bot identify to NickServ?" watcher_ns = "Should the bot identify to NickServ?"
if not self.wmf and self._ask_bool(watcher_ns):
watcher["nickservUsername"] = self._ask("NickServ username:",
watcher["nick"])
watcher["nickservPassword"] = getpass("> Nickserv password: ")
if self.wmf:
watcher["channels"] = ["#{0}.{1}".format(self.lang, self.proj)]
if not self._wmf and self._ask_bool(watcher_ns):
ns_user = self._ask("NickServ username:", watcher["nick"])
ns_pass = self._ask_pass("Nickserv password:")
watcher["nickservUsername"] = ns_user
watcher["nickservPassword"] = ns_pass
if self._wmf:
chan = "#{0}.{1}".format(self._lang, self._proj)
watcher["channels"] = [chan]
else: else:
chan_question = "Watcher channels to join by default:" chan_question = "Watcher channels to join by default:"
watcher["channels"] = self._ask_list(chan_question) watcher["channels"] = self._ask_list(chan_question)
@@ -383,6 +404,7 @@ class ConfigScript(object):
self._set_tasks() self._set_tasks()
if components["wiki_scheduler"]: if components["wiki_scheduler"]:
self._set_schedule() self._set_schedule()
print
self._print("""I am now saving config.yml with your settings. YAML is a self._print("""I am now saving config.yml with your settings. YAML is a
relatively straightforward format and you should be able relatively straightforward format and you should be able
to update these settings in the future when necessary. to update these settings in the future when necessary.


Loading…
Cancel
Save