User:Interwicket/code/botstatus

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Python pywikipedia framework bot code to produce a status report for the bot.

Edit this page, copy everything in between the "source" tags, and save it as botstatus.py in your usual bot working directory.

There are no command line options.

#!/usr/bin/python
# -*- coding: utf-8  -*-

"""
This bot task reads user information from the WMF projects, and writes a status report for
the bot account home wiki.

It intentionally has no parameters, it follows the configuration as loaded by any other task
using the framework.

The status report is always written to User:(username)/Status on the home project. If you want it
somewhere else (on that project), run this program once, then move it, leaving the redirect in place.

The heading for the page is User:(username)/Status/Header and works the same way, you may redirect
it from the /Header subpage of whereever you have moved the page. The /Header page is up to you
to create.

It was written on Python version 2.6.1, with a view to running on Python 3. It may work on
earlier versions.

Any issues, questions, ideas please write on [[:en:wikt:User:Robert Ullmann]] or send "e-mail to user"
from there.

Robert Ullmann, 6.3.9

"""

import wikipedia
import sys
import re
import urllib

from config import mylang, family, usernames 

class FLwiki:
    def __init__(self, code, family):
        self.lc = code
        self.family = family
        self.user = usernames[family][code]
        self.lastcheck = None
        self.status = None
        self.userpage = False
        self.edits = 0
        self.lockedwiki = False
        try: 
            self.site = wikipedia.getSite(code, family)
        except wikipedia.NoSuchSite:
            self.site = None
            self.lockedwiki = True

class FLdict(dict):
    def __init__(self, family):
        self.family = family
    def __missing__(self, code):
        self[code] = FLwiki(code, self.family)
        return self[code]
# so we can just reference the dictionary (;-)

def globalp(flw):
    # predicate that returns True if we are a global bot, and flw is in global bots list

    # check this username, unless (usual case) we already have
    if flw.user != globalp.user:

        # go look at userlist:
        msite = wikipedia.getSite("meta", "meta")
        userurl = urllib.quote(flw.user.encode(flw.site.encoding()))

        gutext = msite.getUrl("/w/index.php?title=Special%3AGlobalUsers&group=Global_bot" +
                         "&limit=1&username=" + userurl)

        # the odd character is a L-to-R mark, in there presumably in case the username is RTL (!)
        globalp.status = (u"<ul><li>" + flw.user + u" \u200e(") in gutext
        globalp.user = flw.user
        if globalp.status: print "(user " + repr(flw.user) + " is a global bot)"
        else: print "(user " + repr(flw.user) + " is not a global bot)"

    if not globalp.status: return False

    # user is global bot, now check wiki:

    if not globalp.text:
        mgp = wikipedia.Page(msite,"Bot policy/Implementation")
        print '(reading global policy page from meta)'
        globalp.text = mgp.get()

    # format is {{/project|fa|wiktionary|aaa=yes|globalbots=yes}}
    if "{{/project|" + flw.lc + "|" + flw.family + "|aaa=yes|globalbots=yes}}" in globalp.text:
                return True
    if "{{/project|" + flw.lc + "|" + flw.family + "|aaa=no|globalbots=yes}}" in globalp.text:
                return True

    # also list of other projects, format :* [[he:Project:Bot policy|hewiki]]
    f2 = flw.family
    if f2 == 'wikipedia': f2 = 'wiki'
    for line in globalp.text.splitlines():
        if line.startswith(':* [[') and '|' + flw.lc + f2 + ']]' in line: return True
    # getting more hacked all the time ...

    return False

globalp.text = None
globalp.user = ''
globalp.status = None

redits = re.compile('editcount="(\d+)"')

def getflstatus(flw):

    # can we just tell caller the status for this one?
    if flw.lockedwiki:
         flw.status = 'blocked'
         print '(%s.%s is locked)' % (flw.lc, flw.family)
         return flw.status

    try:
        ustat = flw.site.getUrl(
             "/w/api.php?action=query&list=users&ususers=" + flw.user +
             "&usprop=blockinfo|groups|editcount&format=xml")
    except Exception, e:
        print "exception trying to read user status from %s.%s:" % (flw.lc, flw.family), str(e)
        flw.status = "exception"
        return "exception"

    # edit count?
    mo = redits.search(ustat)
    if mo: flw.edits = int(mo.group(1))

    # we can be bot, or blocked, or not known:

    # [first line needs improving, other groups will break it]

    if "<g>bot</g>" in ustat: flw.status = "bot"
    if "blockedby=" in ustat: flw.status = "blocked" # over-rides "bot", as it can be both
    if "missing=" in ustat: flw.status = "missing"
    if not flw.status: flw.status = "user"

    # global? overridden by local bot flag or blocked status
    if flw.status == "user" and globalp(flw): flw.status = "globalbot"

    print "%s.%s: %s, %d edits" % (flw.lc, flw.family, flw.status, flw.edits)

    return flw.status

def updstatus(flws, mysite, user):

    try:
        page = wikipedia.Page(mysite, "User:" + user + "/Status")
        text = page.get()
    except wikipedia.NoPage:
        pass
    except wikipedia.IsRedirectPage:
        # follow it
        page = page.getRedirectTarget()
        try:
            text = page.get()
        except Exception, e:
            print "exception getting redirected status page", repr(e)
            return
    except Exception, e:
        print "exception getting status page", repr(e)
        return

    # page text:

    text = "{{/Header}}\n\n"

    for fam in sorted(flws):

        text += "====" + fam + "====\n"
        text += """{| class="wikitable sortable"
! code
! language
! status
! edits
! contributions
|-
"""

        lines = []

        for code in sorted(flws[fam]):
            flw = flws[fam][code]
            if flw.lockedwiki: continue

            # if flw.edits:
            if True:
                if code != mylang: flc = ':' + code + ':'
                else: flc = ''
                if fam != family: flf = fam + ':'
                else: flf = ''
                if flf == 'meta:': flc = ''

                contribs = "[[%s%sSpecial:Contributions/%s|%s]]" % (flc, flf, flw.user, flw.user)
            else: contribs = ''

            lines.append("| '''%s''' || {{#language:%s}} || %s || %d || %s"
                      % (flw.lc, flw.lc, flw.status, flw.edits, contribs))

        text += '\n|-\n'.join(sorted(lines)) + "\n|}\n\n"

    try:
        page.put(text, comment = "update status table")
    except wikipedia.NoPage:
        pass
    except Exception, e:
        print "exception writing status table", str(e)

# main procedure
# for each family with configured usernames, read status from each wiki
# then write report

def main():

    mysite = wikipedia.getSite(mylang, family)
    mysite.forceLogin()

    flws = { }  # dict of FLdict, [fam][code]

    for fam in usernames:
        if not usernames[fam]: continue
        flws[fam] = FLdict(fam)
        for code in usernames[fam]: getflstatus(flws[fam][code])

    me = usernames[family][mylang]
    updstatus(flws, mysite, me)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print "(keyboard interrupt)"
        # mostly just suppress traceback
    finally:
        wikipedia.stopme()