Browse Source

all IRC commands should work now; renamed tasks.py to threads.py to avoid conflicting with bot/tasks/

tags/v0.1^2
Ben Kurtovic 13 years ago
parent
commit
9237a7ed9e
20 changed files with 306 additions and 283 deletions
  1. +3
    -11
      bot/commands/afc_report.py
  2. +34
    -24
      bot/commands/afc_status.py
  3. +6
    -14
      bot/commands/calc.py
  4. +12
    -13
      bot/commands/chanops.py
  5. +15
    -32
      bot/commands/crypt.py
  6. +62
    -47
      bot/commands/git.py
  7. +15
    -9
      bot/commands/help.py
  8. +20
    -22
      bot/commands/link.py
  9. +18
    -17
      bot/commands/remind.py
  10. +20
    -21
      bot/commands/rights.py
  11. +47
    -34
      bot/commands/threads.py
  12. +14
    -7
      bot/tasks/__init__.py
  13. +8
    -8
      bot/wiki/__init__.py
  14. +2
    -2
      bot/wiki/category.py
  15. +1
    -1
      bot/wiki/constants.py
  16. +1
    -1
      bot/wiki/exceptions.py
  17. +11
    -7
      bot/wiki/functions.py
  18. +1
    -1
      bot/wiki/page.py
  19. +9
    -8
      bot/wiki/site.py
  20. +7
    -4
      bot/wiki/user.py

+ 3
- 11
bot/commands/afc_report.py View File

@@ -6,17 +6,9 @@ import urllib


from classes import BaseCommand from classes import BaseCommand


class AFCReport(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Get information about an AFC submission by name."

def check(self, data):
if data.is_command and data.command in ["report", "afc_report"]:
return True
return False
class Command(BaseCommand):
"""Get information about an AFC submission by name."""
name = "report"


def process(self, data): def process(self, data):
self.data = data self.data = data


+ 34
- 24
bot/commands/afc_status.py View File

@@ -1,24 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


"""Report the status of AFC submissions, either as an automatic message on join
or a request via !status."""

import re import re


from classes import BaseCommand from classes import BaseCommand
import config import config
import wiki import wiki


class AFCStatus(BaseCommand):
def get_hooks(self):
return ["join", "msg"]

def get_help(self, command):
return "Get the number of pending AfC submissions, open redirect requests, and open file upload requests."
class Command(BaseCommand):
"""Get the number of pending AfC submissions, open redirect requests, and
open file upload requests."""
name = "status"
hooks = ["join", "msg"]


def check(self, data): def check(self, data):
if data.is_command and data.command in ["status", "count", "num", "number", "afc_status"]:
commands = ["status", "count", "num", "number"]
if data.is_command and data.command in commands:
return True return True

try: try:
if data.line[1] == "JOIN" and data.chan == "#wikipedia-en-afc": if data.line[1] == "JOIN" and data.chan == "#wikipedia-en-afc":
if data.nick != config.irc["frontend"]["nick"]: if data.nick != config.irc["frontend"]["nick"]:
@@ -39,41 +37,48 @@ class AFCStatus(BaseCommand):
action = data.args[0].lower() action = data.args[0].lower()
if action.startswith("sub") or action == "s": if action.startswith("sub") or action == "s":
subs = self.count_submissions() subs = self.count_submissions()
self.connection.reply(data, "there are currently %s pending AfC submissions." % subs)
msg = "there are currently {0} pending AfC submissions."
self.connection.reply(data, msg.format(subs))


elif action.startswith("redir") or action == "r": elif action.startswith("redir") or action == "r":
redirs = self.count_redirects() redirs = self.count_redirects()
self.connection.reply(data, "there are currently %s open redirect requests." % redirs)
msg = "there are currently {0} open redirect requests."
self.connection.reply(data, msg.format(redirs))


elif action.startswith("file") or action == "f": elif action.startswith("file") or action == "f":
files = self.count_redirects() files = self.count_redirects()
self.connection.reply(data, "there are currently %s open file upload requests." % files)
msg = "there are currently {0} open file upload requests."
self.connection.reply(data, msg.format(files))


elif action.startswith("agg") or action == "a": elif action.startswith("agg") or action == "a":
try: try:
agg_num = int(data.args[1]) agg_num = int(data.args[1])
except IndexError: except IndexError:
agg_data = (self.count_submissions(), self.count_redirects(), self.count_files())
agg_data = (self.count_submissions(),
self.count_redirects(), self.count_files())
agg_num = self.get_aggregate_number(agg_data) agg_num = self.get_aggregate_number(agg_data)
except ValueError: except ValueError:
self.connection.reply(data, "\x0303%s\x0301 isn't a number!" % data.args[1])
msg = "\x0303{0}\x0301 isn't a number!"
self.connection.reply(data, msg.format(data.args[1]))
return return
aggregate = self.get_aggregate(agg_num) aggregate = self.get_aggregate(agg_num)
self.connection.reply(data, "aggregate is currently %s (AfC %s)." % (agg_num, aggregate))
msg = "aggregate is currently {0} (AfC {1})."
self.connection.reply(data, msg.format(agg_num, aggregate))


elif action.startswith("join") or action == "j": elif action.startswith("join") or action == "j":
notice = self.get_join_notice() notice = self.get_join_notice()
self.connection.reply(data, notice) self.connection.reply(data, notice)


else: else:
self.connection.reply(data, "unknown argument: \x0303%s\x0301. Valid args are 'subs', 'redirs', 'files', 'agg', and 'join'." % data.args[0])
msg = "unknown argument: \x0303{0}\x0301. Valid args are 'subs', 'redirs', 'files', 'agg', and 'join'."
self.connection.reply(data, msg.format(data.args[0]))


else: else:
subs = self.count_submissions() subs = self.count_submissions()
redirs = self.count_redirects() redirs = self.count_redirects()
files = self.count_files() files = self.count_files()
self.connection.reply(data, "there are currently %s pending submissions, %s open redirect requests, and %s open file upload requests."
% (subs, redirs, files))
msg = "there are currently {0} pending submissions, {1} open redirect requests, and {2} open file upload requests."
self.connection.reply(data, msg.format(subs, redirs, files))


def get_join_notice(self): def get_join_notice(self):
subs = self.count_submissions() subs = self.count_submissions()
@@ -81,20 +86,25 @@ class AFCStatus(BaseCommand):
files = self.count_files() files = self.count_files()
agg_num = self.get_aggregate_number((subs, redirs, files)) agg_num = self.get_aggregate_number((subs, redirs, files))
aggregate = self.get_aggregate(agg_num) aggregate = self.get_aggregate(agg_num)
return ("\x02Current status:\x0F Articles for Creation %s (\x0302AFC\x0301: \x0305%s\x0301; \x0302AFC/R\x0301: \x0305%s\x0301; \x0302FFU\x0301: \x0305%s\x0301)"
% (aggregate, subs, redirs, files))

msg = "\x02Current status:\x0F Articles for Creation {0} (\x0302AFC\x0301: \x0305{1}\x0301; \x0302AFC/R\x0301: \x0305{2}\x0301; \x0302FFU\x0301: \x0305{3}\x0301)"
return msg.format(aggregate, subs, redirs, files)


def count_submissions(self): def count_submissions(self):
"""Returns the number of open AFC submissions (count of CAT:PEND).""" """Returns the number of open AFC submissions (count of CAT:PEND)."""
cat = self.site.get_category("Pending AfC submissions") cat = self.site.get_category("Pending AfC submissions")
subs = cat.members(limit=500)
subs -= 2 # remove [[Wikipedia:Articles for creation/Redirects]] and [[Wikipedia:Files for upload]], which aren't real submissions
subs = len(cat.members(limit=500))

# Remove [[Wikipedia:Articles for creation/Redirects]] and
# [[Wikipedia:Files for upload]], which aren't real submissions:
subs -= 2
return subs return subs


def count_redirects(self): def count_redirects(self):
"""Returns the number of open redirect submissions. Calculated as the """Returns the number of open redirect submissions. Calculated as the
total number of submissions minus the closed ones.""" total number of submissions minus the closed ones."""
content = self.site.get_page("Wikipedia:Articles for creation/Redirects").get()
title = "Wikipedia:Articles for creation/Redirects"
content = self.site.get_page(title).get()
total = len(re.findall("^\s*==(.*?)==\s*$", content, re.MULTILINE)) total = len(re.findall("^\s*==(.*?)==\s*$", content, re.MULTILINE))
closed = content.lower().count("{{afc-c|b}}") closed = content.lower().count("{{afc-c|b}}")
redirs = total - closed redirs = total - closed


+ 6
- 14
bot/commands/calc.py View File

@@ -1,23 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# A somewhat advanced calculator: http://futureboy.us/fsp/frink.fsp.

import re import re
import urllib import urllib


from classes import BaseCommand from classes import BaseCommand


class Calc(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "A somewhat advanced calculator: see http://futureboy.us/fsp/frink.fsp for details."

def check(self, data):
if data.is_command and data.command == "calc":
return True
return False
class Command(BaseCommand):
"""A somewhat advanced calculator: see http://futureboy.us/fsp/frink.fsp
for details."""
name = "calc"


def process(self, data): def process(self, data):
if not data.args: if not data.args:
@@ -27,7 +18,8 @@ class Calc(BaseCommand):
query = ' '.join(data.args) query = ' '.join(data.args)
query = self.cleanup(query) query = self.cleanup(query)


url = "http://futureboy.us/fsp/frink.fsp?fromVal=%s" % urllib.quote(query)
url = "http://futureboy.us/fsp/frink.fsp?fromVal={0}"
url = url.format(urllib.quote(query))
result = urllib.urlopen(url).read() result = urllib.urlopen(url).read()


r_result = re.compile(r'(?i)<A NAME=results>(.*?)</A>') r_result = re.compile(r'(?i)<A NAME=results>(.*?)</A>')


+ 12
- 13
bot/commands/chanops.py View File

@@ -1,31 +1,30 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Voice/devoice/op/deop users in the channel.

from classes import BaseCommand from classes import BaseCommand
import config import config


class ChanOps(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
action = command.capitalize()
return "%s users in the channel." % action
class Command(BaseCommand):
"""Voice, devoice, op, or deop users in the channel."""
name = "chanops"


def check(self, data): def check(self, data):
if data.is_command and data.command in ["voice", "devoice", "op", "deop"]:
commands = ["voice", "devoice", "op", "deop"]
if data.is_command and data.command in commands:
return True return True
return False return False


def process(self, data): def process(self, data):
if data.host not in config.irc["permissions"]["admins"]: if data.host not in config.irc["permissions"]["admins"]:
self.connection.reply(data, "you must be a bot admin to use this command.")
msg = "you must be a bot admin to use this command."
self.connection.reply(data, msg)
return return


if not data.args: # if it is just !op/!devoice/whatever without arguments, assume they want to do this to themselves
# If it is just !op/!devoice/whatever without arguments, assume they
# want to do this to themselves:
if not data.args:
target = data.nick target = data.nick
else: else:
target = data.args[0] target = data.args[0]


self.connection.say("ChanServ", "%s %s %s" % (data.command, data.chan, target))
msg = " ".join((data.command, data.chan, target))
self.connection.say("ChanServ", msg)

+ 15
- 32
bot/commands/crypt.py View File

@@ -1,31 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


"""
Cryptography functions (hashing and cyphers) for EarwigBot IRC.
"""

import hashlib import hashlib


from classes import BaseCommand from classes import BaseCommand
import blowfish import blowfish


class Cryptography(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
if command == "hash":
return ("Return the hash of a string using a given algorithm, " +
"e.g. '!hash sha512 Hello world!'. Use '!hash list' for " +
"a list of supported algorithms.")
elif command == "encrypt":
return ("Encrypt any string with a given key using an " +
"implementation of Blowfish, e.g. '!encrypt some_key " +
"Hello!'.")
else:
return ("Decrypt any string with a given key using an " +
"implementation of Blowfish, e.g. '!decrypt some_key " +
"762cee8a5239548af18275d6c1184f16'.")
class Command(BaseCommand):
"""Provides hash functions with !hash (!hash list for supported algorithms)
and blowfish encryption with !encrypt and !decrypt."""
name = "cryptography"


def check(self, data): def check(self, data):
if data.is_command and data.command in ["hash", "encrypt", "decrypt"]: if data.is_command and data.command in ["hash", "encrypt", "decrypt"]:
@@ -34,31 +17,31 @@ class Cryptography(BaseCommand):


def process(self, data): def process(self, data):
if not data.args: if not data.args:
self.connection.reply(data, "what do you want me to {0}?".format(
data.command))
msg = "what do you want me to {0}?".format(data.command)
self.connection.reply(data, msg)
return return


if data.command == "hash": if data.command == "hash":
algo = data.args[0] algo = data.args[0]
if algo == "list": if algo == "list":
algos = ', '.join(hashlib.algorithms) algos = ', '.join(hashlib.algorithms)
self.connection.reply(data, "supported algorithms: " + algos +
".")
msg = algos.join(("supported algorithms: ", "."))
self.connection.reply(data, msg)
elif algo in hashlib.algorithms: elif algo in hashlib.algorithms:
string = ' '.join(data.args[1:]) string = ' '.join(data.args[1:])
result = eval("hashlib.{0}(string)".format(algo)).hexdigest()
result = getattr(hashlib, algo)(string).hexdigest()
self.connection.reply(data, result) self.connection.reply(data, result)
else: else:
self.connection.reply(data, "unknown algorithm: '{0}'.".format(
algo))
msg = "unknown algorithm: '{0}'.".format(algo)
self.connection.reply(data, msg)


else: else:
key = data.args[0] key = data.args[0]
text = ' '.join(data.args[1:]) text = ' '.join(data.args[1:])


if not text: if not text:
self.connection.reply(data, ("a key was provided, but text " +
"to {0} was not.").format(data.command))
msg = "a key was provided, but text to {0} was not."
self.connection.reply(data, msg.format(data.command))
return return


try: try:
@@ -67,5 +50,5 @@ class Cryptography(BaseCommand):
else: else:
self.connection.reply(data, blowfish.decrypt(key, text)) self.connection.reply(data, blowfish.decrypt(key, text))
except blowfish.BlowfishError as error: except blowfish.BlowfishError as error:
self.connection.reply(data, "{0}: {1}.".format(
error.__class__.__name__, error))
msg = "{0}: {1}.".format(error.__class__.__name__, error)
self.connection.reply(data, msg)

+ 62
- 47
bot/commands/git.py View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Commands to interface with the bot's git repository; use '!git help' for sub-command list.

import shlex import shlex
import subprocess import subprocess
import re import re
@@ -9,26 +7,21 @@ import re
from classes import BaseCommand from classes import BaseCommand
import config import config


class Git(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Commands to interface with the bot's git repository; use '!git help' for sub-command list."

def check(self, data):
if data.is_command and data.command == "git":
return True
return False
class Command(BaseCommand):
"""Commands to interface with the bot's git repository; use '!git help' for
a sub-command list."""
name = "git"


def process(self, data): def process(self, data):
self.data = data self.data = data
if data.host not in config.irc["permissions"]["owners"]: if data.host not in config.irc["permissions"]["owners"]:
self.connection.reply(data, "you must be a bot owner to use this command.")
msg = "you must be a bot owner to use this command."
self.connection.reply(data, msg)
return return


if not data.args: if not data.args:
self.connection.reply(data, "no arguments provided. Maybe you wanted '!git help'?")
msg = "no arguments provided. Maybe you wanted '!git help'?"
self.connection.reply(data, msg)
return return


if data.args[0] == "help": if data.args[0] == "help":
@@ -52,19 +45,20 @@ class Git(BaseCommand):
elif data.args[0] == "status": elif data.args[0] == "status":
self.do_status() self.do_status()


else: # they asked us to do something we don't know
self.connection.reply(data, "unknown argument: \x0303%s\x0301." % data.args[0])
else: # They asked us to do something we don't know
msg = "unknown argument: \x0303{0}\x0301.".format(data.args[0])
self.connection.reply(data, msg)


def exec_shell(self, command): def exec_shell(self, command):
"""execute a shell command and get the output"""
"""Execute a shell command and get the output."""
command = shlex.split(command) command = shlex.split(command)
result = subprocess.check_output(command, stderr=subprocess.STDOUT) result = subprocess.check_output(command, stderr=subprocess.STDOUT)
if result: if result:
result = result[:-1] # strip newline
result = result[:-1] # Strip newline
return result return result


def do_help(self): def do_help(self):
"""display all commands"""
"""Display all commands."""
help_dict = { help_dict = {
"branch": "get current branch", "branch": "get current branch",
"branches": "get all branches", "branches": "get all branches",
@@ -82,21 +76,24 @@ class Git(BaseCommand):
self.connection.reply(self.data, "sub-commands are: %s." % help) self.connection.reply(self.data, "sub-commands are: %s." % help)


def do_branch(self): def do_branch(self):
"""get our current branch"""
"""Get our current branch."""
branch = self.exec_shell("git name-rev --name-only HEAD") branch = self.exec_shell("git name-rev --name-only HEAD")
self.connection.reply(self.data, "currently on branch \x0302%s\x0301." % branch)
msg = "currently on branch \x0302{0}\x0301.".format(branch)
self.connection.reply(self.data, msg)


def do_branches(self): def do_branches(self):
"""get list of branches"""
"""Get a list of branches."""
branches = self.exec_shell("git branch") branches = self.exec_shell("git branch")
branches = branches.replace('\n* ', ', ') # cleanup extraneous characters
# Remove extraneous characters:
branches = branches.replace('\n* ', ', ')
branches = branches.replace('* ', ' ') branches = branches.replace('* ', ' ')
branches = branches.replace('\n ', ', ') branches = branches.replace('\n ', ', ')
branches = branches.strip() branches = branches.strip()
self.connection.reply(self.data, "branches: \x0302%s\x0301." % branches)
msg = "branches: \x0302{0}\x0301.".format(branches)
self.connection.reply(self.data, msg)


def do_checkout(self): def do_checkout(self):
"""switch branches"""
"""Switch branches."""
try: try:
branch = self.data.args[1] branch = self.data.args[1]
except IndexError: # no branch name provided except IndexError: # no branch name provided
@@ -108,15 +105,20 @@ class Git(BaseCommand):
try: try:
result = self.exec_shell("git checkout %s" % branch) result = self.exec_shell("git checkout %s" % branch)
if "Already on" in result: if "Already on" in result:
self.connection.reply(self.data, "already on \x0302%s\x0301!" % branch)
msg = "already on \x0302{0}\x0301!".format(branch)
self.connection.reply(self.data, msg)
else: else:
self.connection.reply(self.data, "switched from branch \x0302%s\x0301 to \x0302%s\x0301." % (current_branch, branch))
ms = "switched from branch \x0302{1}\x0301 to \x0302{1}\x0301."
msg = ms.format(current_branch, branch)
self.connection.reply(self.data, msg)


except subprocess.CalledProcessError: # git couldn't switch branches
self.connection.reply(self.data, "branch \x0302%s\x0301 doesn't exist!" % branch)
except subprocess.CalledProcessError:
# Git couldn't switch branches; assume the branch doesn't exist:
msg = "branch \x0302{0}\x0301 doesn't exist!".format(branch)
self.connection.reply(self.data, msg)


def do_delete(self): def do_delete(self):
"""delete a branch, while making sure that we are not on it"""
"""Delete a branch, while making sure that we are not already on it."""
try: try:
delete_branch = self.data.args[1] delete_branch = self.data.args[1]
except IndexError: # no branch name provided except IndexError: # no branch name provided
@@ -126,38 +128,51 @@ class Git(BaseCommand):
current_branch = self.exec_shell("git name-rev --name-only HEAD") current_branch = self.exec_shell("git name-rev --name-only HEAD")


if current_branch == delete_branch: if current_branch == delete_branch:
self.connection.reply(self.data, "you're currently on this branch; please checkout to a different branch before deleting.")
msg = "you're currently on this branch; please checkout to a different branch before deleting."
self.connection.reply(self.data, msg)
return return


try: try:
self.exec_shell("git branch -d %s" % delete_branch) self.exec_shell("git branch -d %s" % delete_branch)
self.connection.reply(self.data, "branch \x0302%s\x0301 has been deleted locally." % delete_branch)
except subprocess.CalledProcessError: # git couldn't delete
self.connection.reply(self.data, "branch \x0302%s\x0301 doesn't exist!" % delete_branch)
msg = "branch \x0302{0}\x0301 has been deleted locally."
self.connection.reply(self.data, msg.format(delete_branch))
except subprocess.CalledProcessError:
# Git couldn't switch branches; assume the branch doesn't exist:
msg = "branch \x0302{0}\x0301 doesn't exist!".format(delete_branch)
self.connection.reply(self.data, msg)


def do_pull(self): def do_pull(self):
"""pull from remote repository"""
"""Pull from our remote repository."""
branch = self.exec_shell("git name-rev --name-only HEAD") branch = self.exec_shell("git name-rev --name-only HEAD")
self.connection.reply(self.data, "pulling from remote (currently on \x0302%s\x0301)..." % branch)
msg = "pulling from remote (currently on \x0302{0}\x0301)..."
self.connection.reply(self.data, msg.format(branch))


result = self.exec_shell("git pull") result = self.exec_shell("git pull")


if "Already up-to-date." in result: if "Already up-to-date." in result:
self.connection.reply(self.data, "done; no new changes.") self.connection.reply(self.data, "done; no new changes.")
else: else:
changes = re.findall("\s*((.*?)\sfile(.*?)tions?\(-\))", result)[0][0] # find the changes
regex = "\s*((.*?)\sfile(.*?)tions?\(-\))"
changes = re.findall(regex, result)[0][0]
try: try:
remote = self.exec_shell("git config --get branch.%s.remote" % branch)
url = self.exec_shell("git config --get remote.%s.url" % remote)
self.connection.reply(self.data, "done; %s [from %s]." % (changes, url))
except subprocess.CalledProcessError: # something in .git/config is not specified correctly, so we cannot get the remote's url
cmnd_remt = "git config --get branch.{0}.remote".format(branch)
remote = self.exec_shell(cmnd_rmt)
cmnd_url = "git config --get remote.{0}.url".format(remote)
url = self.exec_shell(cmnd_url)
msg = "done; {0} [from {1}].".format(changes, url)
self.connection.reply(self.data, msg)
except subprocess.CalledProcessError:
# Something in .git/config is not specified correctly, so we
# cannot get the remote's URL. However, pull was a success:
self.connection.reply(self.data, "done; %s." % changes) self.connection.reply(self.data, "done; %s." % changes)


def do_status(self): def do_status(self):
"""check whether we have anything to pull"""
last = self.exec_shell("git log -n 1 --pretty=\"%ar\"")
"""Check whether we have anything to pull."""
last = self.exec_shell('git log -n 1 --pretty="%ar"')
result = self.exec_shell("git fetch --dry-run") result = self.exec_shell("git fetch --dry-run")
if not result: # nothing was fetched, so remote and local are equal
self.connection.reply(self.data, "last commit was %s. Local copy is \x02up-to-date\x0F with remote." % last)
if not result: # Nothing was fetched, so remote and local are equal
msg = "last commit was {0}. Local copy is \x02up-to-date\x0F with remote."
self.connection.reply(self.data, msg.format(last))
else: else:
self.connection.reply(self.data, "last local commit was %s. Remote is \x02ahead\x0F of local copy." % last)
msg = "last local commit was {0}. Remote is \x02ahead\x0F of local copy."
self.connection.reply(self.data, msg.format(last))

+ 15
- 9
bot/commands/help.py View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


import re

from classes import BaseCommand, Data from classes import BaseCommand, Data
import commands import commands


@@ -17,7 +19,9 @@ class Command(BaseCommand):
def do_main_help(self, data): def do_main_help(self, data):
"""Give the user a general help message with a list of all commands.""" """Give the user a general help message with a list of all commands."""
msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'." msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'."
msg = msg.format(len(self.cmnds.keys()), ', '.join(self.cmnds))
cmnds = self.cmnds.keys()
cmnds.sort()
msg = msg.format(len(cmnds), ', '.join(cmnds))
self.connection.reply(data, msg) self.connection.reply(data, msg)


def do_command_help(self, data): def do_command_help(self, data):
@@ -26,18 +30,20 @@ class Command(BaseCommand):


# Create a dummy message to test which commands pick up the user's # Create a dummy message to test which commands pick up the user's
# input: # input:
dummy = Data(1)
dummy = Data("PRIVMSG #fake-channel :Fake messsage!")
dummy.command = command.lower() dummy.command = command.lower()
dummy.is_command = True dummy.is_command = True


for cmnd in self.cmnds.values(): for cmnd in self.cmnds.values():
if cmnd.check(dummy):
doc = cmnd.__doc__
if doc:
msg = "info for command \x0303{0}\x0301: \"{1}\""
self.connection.reply(data, msg.format(command, doc))
return
break
if not cmnd.check(dummy):
continue
if cmnd.__doc__:
doc = cmnd.__doc__.replace("\n", "")
doc = re.sub("\s\s+", " ", doc)
msg = "info for command \x0303{0}\x0301: \"{1}\""
self.connection.reply(data, msg.format(command, doc))
return
break


msg = "sorry, no help for \x0303{0}\x0301.".format(command) msg = "sorry, no help for \x0303{0}\x0301.".format(command)
self.connection.reply(data, msg) self.connection.reply(data, msg)

+ 20
- 22
bot/commands/link.py View File

@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Convert a Wikipedia page name into a URL.

import re import re
from urllib import quote


from classes import BaseCommand from classes import BaseCommand


class Link(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Convert a Wikipedia page name into a URL."
class Command(BaseCommand):
"""Convert a Wikipedia page name into a URL."""
name = "link"


def check(self, data): def check(self, data):
if ((data.is_command and data.command == "link") or if ((data.is_command and data.command == "link") or
@@ -37,29 +33,31 @@ class Link(BaseCommand):
self.connection.reply(data, link) self.connection.reply(data, link)


def parse_line(self, line): def parse_line(self, line):
results = list()
results = []


line = re.sub("\{\{\{(.*?)\}\}\}", "", line) # destroy {{{template parameters}}}
# Destroy {{{template parameters}}}:
line = re.sub("\{\{\{(.*?)\}\}\}", "", line)


links = re.findall("(\[\[(.*?)(\||\]\]))", line) # find all [[links]]
# Find all [[links]]:
links = re.findall("(\[\[(.*?)(\||\]\]))", line)
if links: if links:
links = map(lambda x: x[1], links) # re.findall() returns a list of tuples, but we only want the 2nd item in each tuple
results.extend(map(self.parse_link, links))
# re.findall() returns a list of tuples, but we only want the 2nd
# item in each tuple:
links = [i[1] for i in links]
results = map(self.parse_link, links)


templates = re.findall("(\{\{(.*?)(\||\}\}))", line) # find all {{templates}}
# Find all {{templates}}
templates = re.findall("(\{\{(.*?)(\||\}\}))", line)
if templates: if templates:
templates = map(lambda x: x[1], templates)
templates = [i[1] for i in templates]
results.extend(map(self.parse_template, templates)) results.extend(map(self.parse_template, templates))


return results return results


def parse_link(self, pagename): def parse_link(self, pagename):
pagename = pagename.strip()
link = "http://enwp.org/" + pagename
link = link.replace(" ", "_")
return link
link = quote(pagename.replace(" ", "_"), safe="/:")
return "".join(("http://enwp.org/", link))


def parse_template(self, pagename): def parse_template(self, pagename):
pagename = "Template:%s" % pagename # TODO: implement an actual namespace check
link = self.parse_link(pagename)
return link
pagename = "".join(("Template:", pagename))
return self.parse_link(pagename)

+ 18
- 17
bot/commands/remind.py View File

@@ -1,20 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


"""
Set a message to be repeated to you in a certain amount of time.
"""

import threading import threading
import time import time


from classes import BaseCommand from classes import BaseCommand


class Remind(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Set a message to be repeated to you in a certain amount of time."
class Command(BaseCommand):
"""Set a message to be repeated to you in a certain amount of time."""
name = "remind"


def check(self, data): def check(self, data):
if data.is_command and data.command in ["remind", "reminder"]: if data.is_command and data.command in ["remind", "reminder"]:
@@ -23,24 +16,32 @@ class Remind(BaseCommand):


def process(self, data): def process(self, data):
if not data.args: if not data.args:
self.connection.reply(data, "please specify a time (in seconds) and a message in the following format: !remind <time> <msg>.")
msg = "please specify a time (in seconds) and a message in the following format: !remind <time> <msg>."
self.connection.reply(data, msg)
return return


try: try:
wait = int(data.args[0]) wait = int(data.args[0])
except ValueError: except ValueError:
self.connection.reply(data, "the time must be given as an integer, in seconds.")
msg = "the time must be given as an integer, in seconds."
self.connection.reply(data, msg)
return return
message = ' '.join(data.args[1:]) message = ' '.join(data.args[1:])
if not message: if not message:
self.connection.reply(data, "what message do you want me to give you when time is up?")
msg = "what message do you want me to give you when time is up?"
self.connection.reply(data, msg)
return return


end_time = time.strftime("%b %d %H:%M:%S", time.localtime(time.time() + wait))
end_time_with_timezone = time.strftime("%b %d %H:%M:%S %Z", time.localtime(time.time() + wait))
self.connection.reply(data, 'Set reminder for "{0}" in {1} seconds (ends {2}).'.format(message, wait, end_time_with_timezone))
end = time.localtime(time.time() + wait)
end_time = time.strftime("%b %d %H:%M:%S", end)
end_time_with_timezone = time.strftime("%b %d %H:%M:%S %Z", end)

msg = 'Set reminder for "{0}" in {1} seconds (ends {2}).'
msg = msg.format(message, wait, end_time_with_timezone)
self.connection.reply(data, msg)


t_reminder = threading.Thread(target=self.reminder, args=(data, message, wait))
t_reminder = threading.Thread(target=self.reminder,
args=(data, message, wait))
t_reminder.name = "reminder " + end_time t_reminder.name = "reminder " + end_time
t_reminder.daemon = True t_reminder.daemon = True
t_reminder.start() t_reminder.start()


+ 20
- 21
bot/commands/rights.py View File

@@ -1,38 +1,37 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


"""
Retrieve a list of user rights for a given username via the API.
"""

from classes import BaseCommand from classes import BaseCommand
import wiki import wiki


class Rights(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Retrieve a list of rights for a given username."
class Command(BaseCommand):
"""Retrieve a list of rights for a given username."""
name = "rights"


def check(self, data): def check(self, data):
if data.is_command and data.command in ["rights", "groups", "permissions", "privileges"]:
commands = ["rights", "groups", "permissions", "privileges"]
if data.is_command and data.command in commands:
return True return True
return False return False


def process(self, data): def process(self, data):
if not data.args: if not data.args:
self.connection.reply(data, "what user do you want me to look up?")
self.connection.reply(data, "who do you want me to look up?")
return return


username = ' '.join(data.args) username = ' '.join(data.args)
site = wiki.get_site() site = wiki.get_site()
user = site.get_user(username) user = site.get_user(username)
rights = user.groups()
if rights:
try:
rights.remove("*") # remove the implicit '*' group given to everyone
except ValueError:
pass
self.connection.reply(data, "the rights for \x0302{0}\x0301 are {1}.".format(username, ', '.join(rights)))
else:
self.connection.reply(data, "the user \x0302{0}\x0301 has no rights, or does not exist.".format(username))
try:
rights = user.groups()
except wiki.UserNotFoundError:
msg = "the user \x0302{0}\x0301 does not exist."
self.connection.reply(data, msg.format(username))
return

try:
rights.remove("*") # Remove the '*' group given to everyone
except ValueError:
pass
msg = "the rights for \x0302{0}\x0301 are {1}."
self.connection.reply(data, msg.format(username, ', '.join(rights)))

bot/commands/tasks.py → bot/commands/threads.py View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Manage wiki tasks from IRC, and check on thread status.

import threading import threading
import re import re


@@ -9,29 +7,29 @@ from classes import BaseCommand, Data, KwargParseException
import tasks import tasks
import config import config


class Tasks(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Manage wiki tasks from IRC, and check on thread status."
class Command(BaseCommand):
"""Manage wiki tasks from IRC, and check on thread status."""
name = "threads"


def check(self, data): def check(self, data):
if data.is_command and data.command in ["tasks", "task", "threads", "tasklist"]:
commands = ["tasks", "task", "threads", "tasklist"]
if data.is_command and data.command in commands:
return True return True
return False return False


def process(self, data): def process(self, data):
self.data = data self.data = data
if data.host not in config.irc["permissions"]["owners"]: if data.host not in config.irc["permissions"]["owners"]:
self.connection.reply(data, "at this time, you must be a bot owner to use this command.")
msg = "at this time, you must be a bot owner to use this command."
self.connection.reply(data, msg)
return return


if not data.args: if not data.args:
if data.command == "tasklist": if data.command == "tasklist":
self.do_list() self.do_list()
else: else:
self.connection.reply(data, "no arguments provided. Maybe you wanted '!{cmnd} list', '!{cmnd} start', or '!{cmnd} listall'?".format(cmnd=data.command))
msg = "no arguments provided. Maybe you wanted '!{0} list', '!{0} start', or '!{0} listall'?"
self.connection.reply(data, msg.format(data.command))
return return


if data.args[0] == "list": if data.args[0] == "list":
@@ -43,8 +41,9 @@ class Tasks(BaseCommand):
elif data.args[0] in ["listall", "all"]: elif data.args[0] in ["listall", "all"]:
self.do_listall() self.do_listall()


else: # they asked us to do something we don't know
self.connection.reply(data, "unknown argument: \x0303{0}\x0301.".format(data.args[0]))
else: # They asked us to do something we don't know
msg = "unknown argument: \x0303{0}\x0301.".format(data.args[0])
self.connection.reply(data, msg)


def do_list(self): def do_list(self):
"""With !tasks list (or abbreviation !tasklist), list all running """With !tasks list (or abbreviation !tasklist), list all running
@@ -59,43 +58,54 @@ class Tasks(BaseCommand):
tname = thread.name tname = thread.name
if tname == "MainThread": if tname == "MainThread":
tname = self.get_main_thread_name() tname = self.get_main_thread_name()
normal_threads.append("\x0302{0}\x0301 (as main thread, id {1})".format(tname, thread.ident))
t = "\x0302{0}\x0301 (as main thread, id {1})"
normal_threads.append(t.format(tname, thread.ident))
elif tname in ["irc-frontend", "irc-watcher", "wiki-scheduler"]: elif tname in ["irc-frontend", "irc-watcher", "wiki-scheduler"]:
normal_threads.append("\x0302{0}\x0301 (id {1})".format(tname, thread.ident))
t = "\x0302{0}\x0301 (id {1})"
normal_threads.append(t.format(tname, thread.ident))
elif tname.startswith("reminder"): elif tname.startswith("reminder"):
normal_threads.append("\x0302reminder\x0301 (until {0})".format(tname.replace("reminder ", "")))
tname = tname.replace("reminder ", "")
t = "\x0302reminder\x0301 (until {0})"
normal_threads.append(t.format(tname))
else: else:
tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0] tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0]
task_threads.append("\x0302{0}\x0301 (id {1}, since {2})".format(tname, thread.ident, start_time))
t = "\x0302{0}\x0301 (id {1}, since {2})"
task_threads.append(t.format(tname, thread.ident, start_time))


if task_threads: if task_threads:
msg = "\x02{0}\x0F threads active: {1}, and \x02{2}\x0F task threads: {3}.".format(len(threads), ', '.join(normal_threads), len(task_threads), ', '.join(task_threads))
msg = "\x02{0}\x0F threads active: {1}, and \x02{2}\x0F task threads: {3}."
msg = msg.format(len(threads), ', '.join(normal_threads),
len(task_threads), ', '.join(task_threads))
else: else:
msg = "\x02{0}\x0F threads active: {1}, and \x020\x0F task threads.".format(len(threads), ', '.join(normal_threads))
msg = "\x02{0}\x0F threads active: {1}, and \x020\x0F task threads."
msg = msg.format(len(threads), ', '.join(normal_threads))

self.connection.reply(self.data, msg) self.connection.reply(self.data, msg)


def do_listall(self): def do_listall(self):
"""With !tasks listall or !tasks all, list all loaded tasks, and report """With !tasks listall or !tasks all, list all loaded tasks, and report
whether they are currently running or idle.""" whether they are currently running or idle."""
tasks = tasks._tasks.keys()
all_tasks = tasks.get_all().keys()
threads = threading.enumerate() threads = threading.enumerate()
tasklist = [] tasklist = []


tasks.sort()
all_tasks.sort()


for task in tasks:
threads_running_task = [t for t in threads if t.name.startswith(task)]
ids = map(lambda t: str(t.ident), threads_running_task)
for task in all_tasks:
threadlist = [t for t in threads if t.name.startswith(task)]
ids = [str(t.ident) for t in threadlist]
if not ids: if not ids:
tasklist.append("\x0302{0}\x0301 (idle)".format(task)) tasklist.append("\x0302{0}\x0301 (idle)".format(task))
elif len(ids) == 1: elif len(ids) == 1:
tasklist.append("\x0302{0}\x0301 (\x02active\x0F as id {1})".format(task, ids[0]))
t = "\x0302{0}\x0301 (\x02active\x0F as id {1})"
tasklist.append(t.format(task, ids[0]))
else: else:
tasklist.append("\x0302{0}\x0301 (\x02active\x0F as ids {1})".format(task, ', '.join(ids)))
t = "\x0302{0}\x0301 (\x02active\x0F as ids {1})"
tasklist.append(t.format(task, ', '.join(ids)))


tasklist = ", ".join(tasklist) tasklist = ", ".join(tasklist)


msg = "{0} tasks loaded: {1}.".format(len(tasks), tasklist)
msg = "{0} tasks loaded: {1}.".format(len(all_tasks), tasklist)
self.connection.reply(self.data, msg) self.connection.reply(self.data, msg)


def do_start(self): def do_start(self):
@@ -105,26 +115,29 @@ class Tasks(BaseCommand):


try: try:
task_name = data.args[1] task_name = data.args[1]
except IndexError: # no task name given
except IndexError: # No task name given
self.connection.reply(data, "what task do you want me to start?") self.connection.reply(data, "what task do you want me to start?")
return return


try: try:
data.parse_kwargs() data.parse_kwargs()
except KwargParseException, arg: except KwargParseException, arg:
self.connection.reply(data, "error parsing argument: \x0303{0}\x0301.".format(arg))
msg = "error parsing argument: \x0303{0}\x0301.".format(arg)
self.connection.reply(data, msg)
return return


if task_name not in tasks._tasks.keys(): # this task does not exist or hasn't been loaded
self.connection.reply(data, "task could not be found; either wiki/tasks/{0}.py doesn't exist, or it wasn't loaded correctly.".format(task_name))
# This task does not exist or hasn't been loaded:
if task_name not in tasks._tasks.keys():
msg = "task could not be found; either bot/tasks/{0}.py doesn't exist, or it wasn't loaded correctly."
self.connection.reply(data, msg.format(task_name))
return return


tasks.start(task_name, **data.kwargs) tasks.start(task_name, **data.kwargs)
self.connection.reply(data, "task \x0302{0}\x0301 started.".format(task_name))
msg = "task \x0302{0}\x0301 started.".format(task_name)
self.connection.reply(data, msg)


def get_main_thread_name(self): def get_main_thread_name(self):
"""Return the "proper" name of the MainThread; e.g. "irc-frontend" or
"irc-watcher"."""
"""Return the "proper" name of the MainThread."""
if "irc_frontend" in config.components: if "irc_frontend" in config.components:
return "irc-frontend" return "irc-frontend"
elif "wiki_schedule" in config.components: elif "wiki_schedule" in config.components:

+ 14
- 7
bot/tasks/__init__.py View File

@@ -14,7 +14,7 @@ import os


import config import config


__all__ = ["load", "schedule", "start"]
__all__ = ["load", "schedule", "start", "get_all"]


# Store loaded tasks as a dict where the key is the task name and the value is # Store loaded tasks as a dict where the key is the task name and the value is
# an instance of the task class: # an instance of the task class:
@@ -46,14 +46,15 @@ def _wrapper(task, **kwargs):
try: try:
task.run(**kwargs) task.run(**kwargs)
except: except:
print "Task '{0}' raised an exception and had to stop:".format(task.task_name)
error = "Task '{0}' raised an exception and had to stop:"
print error.format(task.task_name)
traceback.print_exc() traceback.print_exc()
else: else:
print "Task '{0}' finished without error.".format(task.task_name) print "Task '{0}' finished without error.".format(task.task_name)


def load(): def load():
"""Load all valid task classes from bot/tasks/, and add them to the
_tasks variable."""
"""Load all valid task classes from bot/tasks/, and add them to the _tasks
variable."""
files = os.listdir(os.path.join("bot", "tasks")) files = os.listdir(os.path.join("bot", "tasks"))
files.sort() # alphabetically sort all files in wiki/tasks/ files.sort() # alphabetically sort all files in wiki/tasks/
for f in files: for f in files:
@@ -83,13 +84,19 @@ def start(task_name, **kwargs):
try: try:
task = _tasks[task_name] task = _tasks[task_name]
except KeyError: except KeyError:
print ("Couldn't find task '{0}': bot/tasks/{0}.py does not exist.").format(task_name)
error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist."
print error.format(task_name)
return return


task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs))
task_thread.name = "{0} ({1})".format(task_name, time.strftime("%b %d %H:%M:%S"))
start_time = time.strftime("%b %d %H:%M:%S")
task_thread.name = "{0} ({1})".format(task_name, start_time)


# stop bot task threads automagically if the main bot stops
# Stop bot task threads automagically if the main bot stops:
task_thread.daemon = True task_thread.daemon = True


task_thread.start() task_thread.start()

def get_all():
"""Return our dict of all loaded tasks."""
return _tasks

+ 8
- 8
bot/wiki/__init__.py View File

@@ -7,14 +7,14 @@ This is a collection of classes and functions to read from and write to
Wikipedia and other wiki sites. No connection whatsoever to python-wikitools Wikipedia and other wiki sites. No connection whatsoever to python-wikitools
written by Mr.Z-man, other than a similar purpose. We share no code. written by Mr.Z-man, other than a similar purpose. We share no code.


Import the toolset with `from wiki import tools`.
Import the toolset with `import wiki`.
""" """


from wiki.tools.constants import *
from wiki.tools.exceptions import *
from wiki.tools.functions import *
from wiki.constants import *
from wiki.exceptions import *
from wiki.functions import *


from wiki.tools.category import Category
from wiki.tools.page import Page
from wiki.tools.site import Site
from wiki.tools.user import User
from wiki.category import Category
from wiki.page import Page
from wiki.site import Site
from wiki.user import User

+ 2
- 2
bot/wiki/category.py View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


from wiki.tools.page import Page
from wiki.page import Page


class Category(Page): class Category(Page):
""" """
@@ -24,7 +24,7 @@ class Category(Page):
up to 500, and bots can go up to 5,000 on a single API query. up to 500, and bots can go up to 5,000 on a single API query.
""" """
params = {"action": "query", "list": "categorymembers", params = {"action": "query", "list": "categorymembers",
"cmlimit": limit, "cmtitle": self.title}
"cmlimit": limit, "cmtitle": self._title}
result = self._site._api_query(params) result = self._site._api_query(params)
members = result['query']['categorymembers'] members = result['query']['categorymembers']
return [member["title"] for member in members] return [member["title"] for member in members]

+ 1
- 1
bot/wiki/constants.py View File

@@ -6,7 +6,7 @@ EarwigBot's Wiki Toolset: Constants
This module defines some useful constants, such as default namespace IDs for This module defines some useful constants, such as default namespace IDs for
easy lookup and our user agent. easy lookup and our user agent.


Import with `from wiki.tools.constants import *`.
Import with `from wiki.constants import *`.
""" """


import platform import platform


+ 1
- 1
bot/wiki/exceptions.py View File

@@ -3,7 +3,7 @@
""" """
EarwigBot's Wiki Toolset: Exceptions EarwigBot's Wiki Toolset: Exceptions


This module contains all exceptions used by the wiki.tools package.
This module contains all exceptions used by the wiki package.
""" """


class WikiToolsetError(Exception): class WikiToolsetError(Exception):


+ 11
- 7
bot/wiki/functions.py View File

@@ -3,11 +3,11 @@
""" """
EarwigBot's Wiki Toolset: Misc Functions EarwigBot's Wiki Toolset: Misc Functions


This module, a component of the wiki.tools package, contains miscellaneous
functions that are not methods of any class, like get_site().
This module, a component of the wiki package, contains miscellaneous functions
that are not methods of any class, like get_site().


There's no need to import this module explicitly. All functions here are There's no need to import this module explicitly. All functions here are
automatically available from wiki.tools.
automatically available from wiki.
""" """


from cookielib import LWPCookieJar, LoadError from cookielib import LWPCookieJar, LoadError
@@ -16,9 +16,9 @@ from getpass import getpass
from os import chmod, path from os import chmod, path
import stat import stat


from core import config
from wiki.tools.exceptions import SiteNotFoundError
from wiki.tools.site import Site
import config
from wiki.exceptions import SiteNotFoundError
from wiki.site import Site


__all__ = ["get_site"] __all__ = ["get_site"]


@@ -84,10 +84,14 @@ def _get_site_object_from_dict(name, d):
article_path = d.get("articlePath") article_path = d.get("articlePath")
script_path = d.get("scriptPath") script_path = d.get("scriptPath")
sql = (d.get("sqlServer"), d.get("sqlDB")) sql = (d.get("sqlServer"), d.get("sqlDB"))
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()


for key, value in namespaces.items(): # Convert string keys to integers
del namespaces[key]
namespaces[int(key)] = value

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)


+ 1
- 1
bot/wiki/page.py View File

@@ -3,7 +3,7 @@
import re import re
from urllib import quote from urllib import quote


from wiki.tools.exceptions import *
from wiki.exceptions import *


class Page(object): class Page(object):
""" """


+ 9
- 8
bot/wiki/site.py View File

@@ -9,11 +9,11 @@ from urllib import unquote_plus, urlencode
from urllib2 import build_opener, HTTPCookieProcessor, URLError from urllib2 import build_opener, HTTPCookieProcessor, URLError
from urlparse import urlparse from urlparse import urlparse


from wiki.tools.category import Category
from wiki.tools.constants import *
from wiki.tools.exceptions import *
from wiki.tools.page import Page
from wiki.tools.user import User
from wiki.category import Category
from wiki.constants import *
from wiki.exceptions import *
from wiki.page import Page
from wiki.user import User


class Site(object): class Site(object):
""" """
@@ -96,8 +96,9 @@ class Site(object):


We'll encode the given params, adding format=json along the way, and We'll encode the given params, adding format=json along the way, and
make the request through self._opener, which has built-in cookie make the request through self._opener, which has built-in cookie
support via self._cookiejar, a User-Agent
(wiki.tools.constants.USER_AGENT), and Accept-Encoding set to "gzip".
support via self._cookiejar, a User-Agent (wiki.constants.USER_AGENT),
and Accept-Encoding set to "gzip".
Assuming everything went well, we'll gunzip the data (if compressed), Assuming everything went well, we'll gunzip the data (if compressed),
load it as a JSON object, and return it. load it as a JSON object, and return it.


@@ -153,7 +154,7 @@ class Site(object):


params = {"action": "query", "meta": "siteinfo"} params = {"action": "query", "meta": "siteinfo"}


if self._namespaces is None or force:
if not self._namespaces or force:
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)


+ 7
- 4
bot/wiki/user.py View File

@@ -2,9 +2,9 @@


from time import strptime from time import strptime


from wiki.tools.constants import *
from wiki.tools.exceptions import UserNotFoundError
from wiki.tools.page import Page
from wiki.constants import *
from wiki.exceptions import UserNotFoundError
from wiki.page import Page


class User(object): class User(object):
""" """
@@ -94,7 +94,10 @@ class User(object):
self._blockinfo = False self._blockinfo = False


self._groups = res["groups"] self._groups = res["groups"]
self._rights = res["rights"].values()
try:
self._rights = res["rights"].values()
except AttributeError:
self._rights = res["rights"]
self._editcount = res["editcount"] self._editcount = res["editcount"]


reg = res["registration"] reg = res["registration"]


Loading…
Cancel
Save