Revision: 6229
Author: russblau
Date: 2009-01-08 16:13:30 +0000 (Thu, 08 Jan 2009)
Log Message:
-----------
Branch for conversion to new framework.
Added Paths:
-----------
branches/rewrite/pywikibot/scripts/category.py
Copied: branches/rewrite/pywikibot/scripts/category.py (from rev 6214, trunk/pywikipedia/category.py)
===================================================================
--- branches/rewrite/pywikibot/scripts/category.py (rev 0)
+++ branches/rewrite/pywikibot/scripts/category.py 2009-01-08 16:13:30 UTC (rev 6229)
@@ -0,0 +1,999 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Scripts to manage categories.
+
+Syntax: python category.py action [-option]
+
+where action can be one of these:
+ * add - mass-add a category to a list of pages
+ * remove - remove category tag from all pages in a category
+ * move - move all pages in a category to another category
+ * tidy - tidy up a category by moving its articles into subcategories
+ * tree - show a tree of subcategories of a given category
+ * listify - make a list of all of the articles that are in a category
+
+and option can be one of these:
+ * -person - sort persons by their last name (for action 'add')
+ * -rebuild - reset the database
+ * -from: - The category to move from (for the move option)
+ Also, the category to remove from in the remove option
+ Also, the category to make a list of in the listify option
+ * -to: - The category to move to (for the move option)
+ - Also, the name of the list to make in the listify option
+ NOTE: If the category names have spaces in them you may need to use
+ a special syntax in your shell so that the names aren't treated as
+ separate parameters. For instance, in BASH, use single quotes,
+ e.g. -from:'Polar bears'
+ * -batch - Don't prompt to delete emptied categories (do it
+ automatically).
+ * -summary: - Pick a custom edit summary for the bot.
+ * -inplace - Use this flag to change categories in place rather than
+ rearranging them.
+ * -nodelsum - An option for remove, this specifies not to use the custom
+ edit summary as the deletion reason. Instead, it uses the
+ default deletion reason for the language, which is "Category
+ was disbanded" in English.
+ * -overwrite - An option for listify, this overwrites the current page with
+ the list even if something is already there.
+ * -showimages - An option for listify, this displays images rather than
+ linking them in the list.
+ * -talkpages - An option for listify, this outputs the links to talk pages
+ of the pages to be listified in addition to the pages
+ themselves.
+ * -recurse - Recurse through all subcategories of categories.
+ * -match - Only work on pages whose titles match the given regex (for
+ move and remove actions).
+
+If action is "add", the following options are supported:
+
+¶ms;
+
+For the actions tidy and tree, the bot will store the category structure
+locally in category.dump. This saves time and server load, but if it uses
+these data later, they may be outdated; use the -rebuild parameter in this
+case.
+
+For example, to create a new category from a list of persons, type:
+
+ python category.py add -person
+
+and follow the on-screen instructions.
+
+Or to do it all from the command-line, use the following syntax:
+
+ python category.py move -from:US -to:'United States'
+
+This will move all pages in the category US to the category United States.
+
+"""
+
+#
+# (C) Rob W.W. Hooft, 2004
+# (C) Daniel Herding, 2004
+# (C) Anreas J Schwab, 2007
+#
+__version__ = '$Id$'
+#
+# Distributed under the terms of the MIT license.
+#
+import os, re, sys, pickle, bz2
+import wikipedia, catlib, config, pagegenerators
+
+# This is required for the text that is shown when you run this script
+# with the parameter -help.
+docuReplacements = {
+ '¶ms;': pagegenerators.parameterHelp
+}
+# Summary messages
+msg_add={
+ 'ar':u'روبوت: إضافة [[تصنيف:%s]]',
+ 'bat-smg':u'Robots: Pridedama [[Kateguorėjė:%s]]',
+ 'ca':u'Robot: Afegint [[Categoria:%s]]',
+ 'da':u'Robot: Tilføjer [[Kategori:%s]]',
+ 'de':u'Bot: Ergänze [[Kategorie:%s]]',
+ 'en':u'Robot: Adding [[Category:%s]]',
+ 'es':u'Bot: Añadida [[Categoría:%s]]',
+ 'id':u'Bot: Menambahkan [[Kategori:%s]]',
+ 'fi':u'Botti lisäsi luokkaan [[Luokka:%s]]',
+ 'fr':u'Robot : ajoute [[Catégorie:%s]]',
+ 'he':u'בוט: מוסיף [[קטגוריה:%s]]',
+ 'ia':u'Robot: Addition de [[Categoria:%s]]',
+ 'is':u'Vélmenni: Bæti við [[Flokkur:%s]]',
+ 'it':u'Bot: Aggiungo [[Categoria:%s]]',
+ 'ja':u'ロボットによる: カテゴリ追加 [[Category:%s]]',
+ 'kk':u'Бот: [[Санат:%s]] үстеді',
+ 'ko': u'로봇: [[분류:%s]] 추가',
+ 'ksh':u'Bot: [[Saachjropp:%s]] erinjedonn',
+ 'lb': u'Bot: Derbäi setzen [[Kategorie:%s]]',
+ 'lt':u'robotas: Pridedama [[Kategorija:%s]]',
+ 'nds':u'Kat-Bot: [[Kategorie:%s]] rin',
+ 'nds-nl':u'bot: [[kattegerie:%s]] derbie edaon',
+ 'nl':u'Bot: [[Categorie:%s]] toegevoegd',
+ 'no':u'Robot: Legger til [[Kategori:%s]]',
+ 'nn':u'robot: la til [[Kategori:%s]]',
+ 'pl':u'Robot dodaje [[Kategoria:%s]]',
+ 'pt':u'Bot: Adicionando [[Categoria:%s]]',
+ 'ru':u'Робот: добавление [[Категория:%s]]',
+ 'sr':u'Бот: Додаје [[Категорија:%s]]',
+ 'sv':u'Robot: Lägger till [[Kategori:%s]]',
+ 'zh':u'機器人:新增目錄 [[Category:%s]]',
+ }
+
+msg_change={
+ 'ar':u'روبوت: تغيير %s',
+ 'ca':u'Robot: Canviant %s',
+ 'da':u'Robot: Ændrer %s',
+ 'de':u'Bot: Ändere %s',
+ 'en':u'Robot: Changing %s',
+ 'es':u'Bot: Cambiada %s',
+ 'id':u'Bot: Mengganti %s',
+ 'fi':u'Botti muutti luokan %s',
+ 'fr':u'Robot : modifie [[%s]]',
+ 'he':u'בוט: משנה %s',
+ 'ia':u'Robot: Modification de %s',
+ 'is':u'Vélmenni: Breyti flokknum [[%s]]',
+ 'it':u'Bot: Modifico %s',
+ 'lt':u'robotas: Keičiama %s',
+ 'ja':u'ロボットによる: カテゴリ変更 [[%s]]',
+ 'kk':u'Бот: %s дегенді түзетті',
+ 'ko': u'로봇: %s 수정',
+ 'ksh':u'Bot: %s ußjewääßelt',
+ 'nds':u'Kat-Bot: %s utwesselt',
+ 'nds-nl':u'bot: wieziging %s',
+ 'nl':u'Bot: Wijziging %s',
+ 'no':u'Robot: Endrer %s',
+ 'nn':u'robot: endra %s',
+ 'pt':u'Bot: Modificando [[%s]]',
+ 'pl':u'Robot przenosi %s',
+ 'ru':u'Робот: изменение %s',
+ 'sr':u'Бот: Измена категорије %s',
+ 'sv':u'Robot: Ändrar %s',
+ 'zh':u'機器人:變更目錄 [[%s]]',
+ }
+
+deletion_reason_move = {
+ 'ar':u'روبوت: التصنيف نقل إلى [[:تصنيف:%s|%s]]',
+ 'bat-smg':u'Robots: Kateguorėjė bova parvadėnta i [[:Kateguorėjė:%s|%s]]',
+ 'ca':u'Robot: La categoria s\'ha mogut a [[:Categoria:%s|%s]]',
+ 'da':u'Robot: Kategori flyttet til [[:Category:%s|%s]]',
+ 'de':u'Bot: Kategorie wurde nach [[:Category:%s|%s]] verschoben',
+ 'en':u'Robot: Category was moved to [[:Category:%s|%s]]',
+ 'es':u'Robot: La categoría ha sido movida a [[:Category:%s|%s]]',
+ 'fi':u'Botti siirsi luokan nimelle [[:Luokka:%s|%s]]',
+ 'fr':u'Robot : catégorie déplacée sur [[:Category:%s|%s]]',
+ 'he':u'בוט: הקטגוריה הועברה לשם [[:קטגוריה:%s|%s]]',
+ 'ia':u'Robot: Categoria transferite a [[:Category:%s|%s]]',
+ 'id':u'Bot: Kategori dipindahkan ke [[:Category:%s|%s]]',
+ 'it':u'Bot: La categoria è stata sostituita da [[:Categoria:%s|%s]]',
+ 'ja':u'ロボットによる: カテゴリ [[:Category:%s]]へ移動',
+ 'kk':u'Бот: Санат [[:Санат:%s|%s]] дегенге жылжытылды',
+ 'ko': u'로봇: 분류가 [[:분류:%s|%s]]로 옮겨짐',
+ 'ksh':u'Bot: Saachjropp noh [[:Category:%s|%s]] jeschovve',
+ 'lb': u'Bot: Kategorie gouf gréckelt: Nei [[:Kategorie:%s|%s]]',
+ 'lt':u'robotas: Kategorija pervadinta į [[:Category:%s|%s]]',
+ 'nds':u'Kat-Bot: Kategorie na [[:Category:%s|%s]] schaven',
+ 'nds-nl':u'Bot: kattegerie is herneumd naor [[:Kattegerie:%s|%s]]',
+ 'nl':u'Bot: Categorie is hernoemd naar [[:Category:%s|%s]]',
+ 'no':u'Robot: Kategorien ble flyttet til [[:Category:%s|%s]]',
+ 'nn':u'robot: kategorien blei flytta til [[:Kategori:%s|%s]]',
+ 'pt':u'Bot: Categoria [[:Category:%s|%s]] foi movida',
+ 'pl':u'Robot przenosi kategorię do [[:Category:%s|%s]]',
+ 'ru':u'Робот: категория переименована в [[:Категория:%s|%s]]',
+ 'sr':u'Бот: Категорија премештена у [[:Category:%s|%s]]',
+ 'sv':u'Robot: Kategori flyttades till [[:Category:%s|%s]]',
+ 'zh':u'機器人:移動目錄至 [[:Category:%s|%s]]',
+ }
+
+cfd_templates = {
+ 'en':['cfd', 'cfr', 'cfru', 'cfr-speedy', 'cfm', 'cfdu'],
+ 'fi':['roskaa', 'poistettava', 'korjattava/nimi', u'yhdistettäväLuokka'],
+ 'he':[u'הצבעת מחיקה', u'למחוק'],
+ }
+
+class CategoryDatabase:
+ '''
+ This is a temporary knowledge base saving for each category the contained
+ subcategories and articles, so that category pages do not need to
+ be loaded over and over again
+ '''
+ def __init__(self, rebuild = False, filename = 'category.dump.bz2'):
+ if rebuild:
+ self.rebuild()
+ else:
+ try:
+ if not os.path.isabs(filename):
+ filename = wikipedia.config.datafilepath(filename)
+ f = bz2.BZ2File(filename, 'r')
+ wikipedia.output(u'Reading dump from %s'
+ % wikipedia.config.shortpath(filename))
+ databases = pickle.load(f)
+ f.close()
+ # keys are categories, values are 2-tuples with lists as entries.
+ self.catContentDB = databases['catContentDB']
+ # like the above, but for supercategories
+ self.superclassDB = databases['superclassDB']
+ del databases
+ except:
+ # If something goes wrong, just rebuild the database
+ self.rebuild()
+
+ def rebuild(self):
+ self.catContentDB={}
+ self.superclassDB={}
+
+ def getSubcats(self, supercat):
+ '''
+ For a given supercategory, return a list of Categorys for all its
+ subcategories.
+ Saves this list in a temporary database so that it won't be loaded from the
+ server next time it's required.
+ '''
+ # if we already know which subcategories exist here
+ if self.catContentDB.has_key(supercat):
+ return self.catContentDB[supercat][0]
+ else:
+ subcatlist = supercat.subcategoriesList()
+ articlelist = supercat.articlesList()
+ # add to dictionary
+ self.catContentDB[supercat] = (subcatlist, articlelist)
+ return subcatlist
+
+ def getArticles(self, cat):
+ '''
+ For a given category, return a list of Pages for all its articles.
+ Saves this list in a temporary database so that it won't be loaded from the
+ server next time it's required.
+ '''
+ # if we already know which articles exist here
+ if self.catContentDB.has_key(cat):
+ return self.catContentDB[cat][1]
+ else:
+ subcatlist = cat.subcategoriesList()
+ articlelist = cat.articlesList()
+ # add to dictionary
+ self.catContentDB[cat] = (subcatlist, articlelist)
+ return articlelist
+
+ def getSupercats(self, subcat):
+ # if we already know which subcategories exist here
+ if self.superclassDB.has_key(subcat):
+ return self.superclassDB[subcat]
+ else:
+ supercatlist = subcat.supercategoriesList()
+ # add to dictionary
+ self.superclassDB[subcat] = supercatlist
+ return supercatlist
+
+ def dump(self, filename = 'category.dump.bz2'):
+ '''
+ Saves the contents of the dictionaries superclassDB and catContentDB to disk.
+ '''
+ if not os.path.isabs(filename):
+ filename = wikipedia.config.datafilepath(filename)
+ wikipedia.output(u'Dumping to %s, please wait...'
+ % wikipedia.config.shortpath(filename))
+ f = bz2.BZ2File(filename, 'w')
+ databases = {
+ 'catContentDB': self.catContentDB,
+ 'superclassDB': self.superclassDB
+ }
+ # store dump to disk in binary format
+ try:
+ pickle.dump(databases, f, protocol=pickle.HIGHEST_PROTOCOL)
+ except pickle.PicklingError:
+ pass
+ f.close()
+
+def sorted_by_last_name(catlink, pagelink):
+ '''Return a Category with key that sorts persons by their last names.
+
+ Parameters: catlink - The Category to be linked
+ pagelink - the Page to be placed in the category
+
+ Trailing words in brackets will be removed. Example: If
+ category_name is 'Author' and pl is a Page to [[Alexandre Dumas
+ (senior)]], this function will return this Category:
+ [[Category:Author|Dumas, Alexandre]]
+
+ '''
+ page_name = pagelink.title()
+ site = pagelink.site()
+ # regular expression that matches a name followed by a space and
+ # disambiguation brackets. Group 1 is the name without the rest.
+ bracketsR = re.compile('(.*) \(.+?\)')
+ match_object = bracketsR.match(page_name)
+ if match_object:
+ page_name = match_object.group(1)
+ split_string = page_name.split(' ')
+ if len(split_string) > 1:
+ # pull last part of the name to the beginning, and append the
+ # rest after a comma; e.g., "John von Neumann" becomes
+ # "Neumann, John von"
+ sorted_key = split_string[-1] + ', ' + ' '.join(split_string[:-1])
+ # give explicit sort key
+ return wikipedia.Page(site, catlink.title() + '|' + sorted_key)
+ else:
+ return wikipedia.Page(site, catlink.title())
+
+def add_category(sort_by_last_name = False):
+ '''A robot to mass-add a category to a list of pages.'''
+ site = wikipedia.getSite()
+ if gen:
+ newcatTitle = wikipedia.input(
+ u'Category to add (do not give namespace):')
+ if not site.nocapitalize:
+ newcatTitle = newcatTitle[:1].capitalize() + newcatTitle[1:]
+
+ # set edit summary message
+ wikipedia.setAction(wikipedia.translate(site, msg_add) % newcatTitle)
+
+ cat_namespace = site.category_namespaces()[0]
+
+ answer = ''
+ for page in gen:
+ if answer != 'a':
+ answer = ''
+
+ while answer not in ('y','n','a'):
+ answer = wikipedia.input(u'%s [y/n/a(ll)]:' % (page.aslink()))
+ if answer == 'a':
+ confirm = ''
+ while confirm not in ('y','n'):
+ confirm = wikipedia.input(u"""\
+This should be used if and only if you are sure that your links are correct!
+Are you sure? [y/n]:""")
+ if confirm == 'n':
+ answer = ''
+
+ if answer == 'y' or answer == 'a':
+ try:
+ text = page.get()
+ except wikipedia.NoPage:
+ wikipedia.output(u"%s doesn't exist yet. Ignoring."
+ % (page.title()))
+ pass
+ except wikipedia.IsRedirectPage, arg:
+ redirTarget = wikipedia.Page(site, arg.args[0])
+ wikipedia.output(
+ u"WARNING: %s is redirect to %s. Ignoring."
+ % (page.title(), redirTarget.title()))
+ else:
+ cats = page.categories()
+ # Show the title of the page we're working on.
+ # Highlight the title in purple.
+ wikipedia.output(
+ u"\n\n>>> \03{lightpurple}%s\03{default} <<<"
+ % page.title())
+ wikipedia.output(u"Current categories:")
+ for cat in cats:
+ wikipedia.output(u"* %s" % cat.title())
+ catpl = wikipedia.Page(site,
+ cat_namespace + ':' + newcatTitle)
+ if sort_by_last_name:
+ catpl = sorted_by_last_name(catpl, page)
+ if catpl in cats:
+ wikipedia.output(u"%s is already in %s."
+ % (page.title(), catpl.title()))
+ else:
+ wikipedia.output(u'Adding %s' % catpl.aslink())
+ cats.append(catpl)
+ text = page.get()
+ text = wikipedia.replaceCategoryLinks(text, cats)
+ try:
+ page.put(text)
+ except wikipedia.EditConflict:
+ wikipedia.output(
+ u'Skipping %s because of edit conflict'
+ % (page.title()))
+
+class CategoryMoveRobot:
+ """Robot to move pages from one category to another."""
+ def __init__(self, oldCatTitle, newCatTitle, batchMode=False,
+ editSummary='', inPlace=False, moveCatPage=True,
+ deleteEmptySourceCat=True, titleRegex=None):
+ site = wikipedia.getSite()
+ self.editSummary = editSummary
+ self.oldCat = catlib.Category(site, 'Category:' + oldCatTitle)
+ self.newCatTitle = newCatTitle
+ self.inPlace = inPlace
+ self.moveCatPage = moveCatPage
+ self.batchMode = batchMode
+ self.deleteEmptySourceCat = deleteEmptySourceCat
+ self.titleRegex = titleRegex
+ # set edit summary message
+ if self.editSummary:
+ wikipedia.setAction(self.editSummary)
+ else:
+ wikipedia.setAction(wikipedia.translate(site, msg_change)
+ % self.oldCat.title())
+
+ def run(self):
+ site = wikipedia.getSite()
+ newCat = catlib.Category(site, 'Category:' + self.newCatTitle)
+
+ # Copy the category contents to the new category page
+ copied = False
+ oldMovedTalk = None
+ if self.oldCat.exists() and self.moveCatPage:
+ copied = self.oldCat.copyAndKeep(
+ self.newCatTitle,
+ wikipedia.translate(site, cfd_templates))
+ # Also move the talk page
+ if copied:
+ reason = wikipedia.translate(site, deletion_reason_move) \
+ % (self.newCatTitle, self.newCatTitle)
+ oldTalk = self.oldCat.toggleTalkPage()
+ if oldTalk.exists():
+ newTalkTitle = newCat.toggleTalkPage().title()
+ try:
+ talkMoved = oldTalk.move(newTalkTitle, reason)
+ except (wikipedia.NoPage, wikipedia.PageNotSaved), e:
+ #in order :
+ #Source talk does not exist, or
+ #Target talk already exists
+ wikipedia.output(e.message)
+ else:
+ if talkMoved:
+ oldMovedTalk = oldTalk
+
+ # Move articles
+ gen = pagegenerators.CategorizedPageGenerator(self.oldCat,
+ recurse=False)
+ preloadingGen = pagegenerators.PreloadingGenerator(gen)
+ for article in preloadingGen:
+ if not self.titleRegex or re.search(self.titleRegex,
+ article.title()):
+ catlib.change_category(article, self.oldCat, newCat,
+ inPlace=self.inPlace)
+
+ # Move subcategories
+ gen = pagegenerators.SubCategoriesPageGenerator(self.oldCat,
+ recurse=False)
+ preloadingGen = pagegenerators.PreloadingGenerator(gen)
+ for subcategory in preloadingGen:
+ if not self.titleRegex or re.search(self.titleRegex,
+ subcategory.title()):
+ catlib.change_category(subcategory, self.oldCat, newCat,
+ inPlace=self.inPlace)
+
+ # Delete the old category and its moved talk page
+ if copied and self.deleteEmptySourceCat == True:
+ if self.oldCat.isEmpty():
+ reason = wikipedia.translate(site, deletion_reason_move) \
+ % (self.newCatTitle, self.newCatTitle)
+ confirm = not self.batchMode
+ self.oldCat.delete(reason, confirm, mark = True)
+ if oldMovedTalk is not None:
+ oldMovedTalk.delete(reason, confirm, mark = True)
+ else:
+ wikipedia.output('Couldn\'t delete %s - not empty.'
+ % self.oldCat.title())
+
+
+class CategoryListifyRobot:
+ '''
+ Creates a list containing all of the members in a category.
+ '''
+ listify_msg={
+ 'ar':u'روبوت: عرض من %s (%d مدخلة)',
+ 'ca':u'Robot: Llistant de %s (%d entrades)',
+ 'en':u'Robot: Listifying from %s (%d entries)',
+ 'fi':u'Botti listasi luokan %s (%d jäsentä)',
+ 'he':u'בוט: יוצר רשימה מהקטגוריה %s (%d דפים)',
+ 'kk':u'Бот: %s дегеннен (%d буын) тізімдеді',
+ 'nds-nl':u'Bot: lieste van %s (%d pagina\'s)',
+ 'nl':u'Bot: Lijst van %s (%d pagina\'s)',
+ 'sv':u'Robot: Skapar en lista från %s (%d)',
+ 'pt':u'Bot: Listando de %s (%d entradas)',
+ 'zh':u'機器人: 從%s提取列表(%d個項目)',
+ }
+
+ def __init__(self, catTitle, listTitle, editSummary, overwrite = False, showImages = False, subCats = False, talkPages = False, recurse = False):
+ self.editSummary = editSummary
+ self.overwrite = overwrite
+ self.showImages = showImages
+ self.cat = catlib.Category(wikipedia.getSite(), 'Category:' + catTitle)
+ self.list = wikipedia.Page(wikipedia.getSite(), listTitle)
+ self.subCats = subCats
+ self.talkPages = talkPages
+ self.recurse = recurse
+
+ def run(self):
+ listOfArticles = self.cat.articlesList(recurse = self.recurse)
+ if self.subCats:
+ listOfArticles += self.cat.subcategoriesList()
+ if self.editSummary:
+ wikipedia.setAction(self.editSummary)
+ else:
+ wikipedia.setAction(wikipedia.translate(wikipedia.getSite(), self.listify_msg) % (self.cat.title(), len(listOfArticles)))
+
+ listString = ""
+ for article in listOfArticles:
+ if (not article.isImage() or self.showImages) and not article.isCategory():
+ if self.talkPages and not article.isTalkPage():
+ listString = listString + "*[[%s]] -- [[%s|talk]]\n" % (article.title(), article.toggleTalkPage().title())
+ else:
+ listString = listString + "*[[%s]]\n" % article.title()
+ else:
+ if self.talkPages and not article.isTalkPage():
+ listString = listString + "*[[:%s]] -- [[%s|talk]]\n" % (article.title(), article.toggleTalkPage().title())
+ else:
+ listString = listString + "*[[:%s]]\n" % article.title()
+ if self.list.exists() and not self.overwrite:
+ wikipedia.output(u'Page %s already exists, aborting.' % self.list.title())
+ else:
+ self.list.put(listString)
+
+class CategoryRemoveRobot:
+ '''
+ Removes the category tag from all pages in a given category and from the
+ category pages of all subcategories, without prompting.
+ Does not remove category tags pointing at subcategories.
+ '''
+ deletion_reason_remove = {
+ 'ar':u'روبوت: التصنيف تم الاستغناء عنه',
+ 'ca':u'Robot: La categoria s\'ha eliminat',
+ 'da':u'Robot: Kategorien blev opløst',
+ 'de':u'Bot: Kategorie wurde aufgelöst',
+ 'en':u'Robot: Category was disbanded',
+ 'es':u'Robot: La categoría ha sido eliminada',
+ 'fi':u'Botti tyhjensi luokan',
+ 'he':u'בוט: הקטגוריה פורקה',
+ 'ia':u'Robot: Categoria esseva dissolvite',
+ 'kk':u'Бот: Санат тарқатылды',
+ 'ksh':u'Bot: de Saachjropp is nu opjelööß',
+ 'nds':u'Kat-Bot: Kategorie is nu oplööst',
+ 'nds-nl':u'Bot: kattegerie besteet neet meer',
+ 'nl':u'Bot: Categorie is opgeheven',
+ 'no':u'Robot: Kategorien ble oppløst',
+ 'nn':u'robot: kategorien blei løyst opp',
+ 'pt':u'Bot: Categoria foi unida',
+ 'ru':u'Робот: категория расформирована',
+ 'sv':u'Robot: Kategorin upplöstes',
+ 'zh':u'機器人:本目錄已解散',
+ }
+
+ msg_remove={
+ 'ar':u'روبوت: إزالة من %s',
+ 'bat-smg':u'Robots: Trėnama ėš %s',
+ 'ca':u'Robot: Eliminant de %s',
+ 'da':u'Robot: Fjerner fra %s',
+ 'de':u'Bot: Entferne aus %s',
+ 'en':u'Robot: Removing from %s',
+ 'es':u'Bot: Eliminada de la %s',
+ 'fi':u'Botti poisti luokasta %s',
+ 'he':u'בוט: מסיר את הדף מהקטגוריה %s',
+ 'ia':u'Robot: Eliminate de %s',
+ 'is':u'Vélmenni: Fjarlægi [[Flokkur:%s]]',
+ 'kk':u'Бот: %s дегеннен аластатты',
+ 'ksh':u'Bot: uß de %s ußjedraare',
+ 'lb': u'Bot: Ewech huele vun %s',
+ 'nds':u'Kat-Bot: rut ut %s',
+ 'nds-nl':u'Bot: vort-ehaold uut %s',
+ 'nl':u'Bot: Verwijderd uit %s',
+ 'no':u'Robot: Fjerner ifra %s',
+ 'nn':u'robot: fjerna ifrå %s',
+ 'pt':u'Bot: Removendo [[Categoria:%s]]',
+ 'ru':u'Робот: исключение из [[Категория:%s]]',
+ 'sr':u'Бот: Уклањање из категорије [[Категорија:%s|%s]]',
+ 'sv':u'Robot: Tar bort från %s',
+ 'zh':u'機器人:移除目錄%s',
+ }
+
+ def __init__(self, catTitle, batchMode = False, editSummary = '', useSummaryForDeletion = True, titleRegex = None, inPlace = False):
+ self.editSummary = editSummary
+ self.cat = catlib.Category(wikipedia.getSite(), 'Category:' + catTitle)
+ # get edit summary message
+ self.useSummaryForDeletion = useSummaryForDeletion
+ self.batchMode = batchMode
+ self.titleRegex = titleRegex
+ self.inPlace = inPlace
+ if self.editSummary:
+ wikipedia.setAction(self.editSummary)
+ else:
+ wikipedia.setAction(wikipedia.translate(wikipedia.getSite(), self.msg_remove) % self.cat.title())
+
+ def run(self):
+ articles = self.cat.articlesList(recurse = 0)
+ if len(articles) == 0:
+ wikipedia.output(u'There are no articles in category %s' % self.cat.title())
+ else:
+ for article in articles:
+ if not self.titleRegex or re.search(self.titleRegex,article.title()):
+ catlib.change_category(article, self.cat, None, inPlace = self.inPlace)
+ # Also removes the category tag from subcategories' pages
+ subcategories = self.cat.subcategoriesList(recurse = 0)
+ if len(subcategories) == 0:
+ wikipedia.output(u'There are no subcategories in category %s' % self.cat.title())
+ else:
+ for subcategory in subcategories:
+ catlib.change_category(subcategory, self.cat, None, inPlace = self.inPlace)
+ # Deletes the category page
+ if self.cat.exists() and self.cat.isEmpty():
+ if self.useSummaryForDeletion and self.editSummary:
+ reason = self.editSummary
+ else:
+ reason = wikipedia.translate(wikipedia.getSite(), self.deletion_reason_remove)
+ talkPage = self.cat.toggleTalkPage()
+ self.cat.delete(reason, not self.batchMode)
+ if (talkPage.exists()):
+ talkPage.delete(reason=reason, prompt=not self.batchMode)
+
+class CategoryTidyRobot:
+ """
+ Script to help a human to tidy up a category by moving its articles into
+ subcategories
+
+ Specify the category name on the command line. The program will pick up the
+ page, and look for all subcategories and supercategories, and show them with
+ a number adjacent to them. It will then automatically loop over all pages
+ in the category. It will ask you to type the number of the appropriate
+ replacement, and perform the change robotically.
+
+ If you don't want to move the article to a subcategory or supercategory, but to
+ another category, you can use the 'j' (jump) command.
+
+ Typing 's' will leave the complete page unchanged.
+
+ Typing '?' will show you the first few bytes of the current page, helping
+ you to find out what the article is about and in which other categories it
+ currently is.
+
+ Important:
+ * this bot is written to work with the MonoBook skin, so make sure your bot
+ account uses this skin
+ """
+ def __init__(self, catTitle, catDB):
+ self.catTitle = catTitle
+ self.catDB = catDB
+
+ def move_to_category(self, article, original_cat, current_cat):
+ '''
+ Given an article which is in category original_cat, ask the user if
+ it should be moved to one of original_cat's subcategories.
+ Recursively run through subcategories' subcategories.
+ NOTE: current_cat is only used for internal recursion. You should
+ always use current_cat = original_cat.
+ '''
+ wikipedia.output(u'')
+ # Show the title of the page where the link was found.
+ # Highlight the title in purple.
+ wikipedia.output(u'Treating page \03{lightpurple}%s\03{default}, currently in \03{lightpurple}%s\03{default}' % (article.title(), current_cat.title()))
+
+ # Determine a reasonable amount of context to print
+ try:
+ full_text = article.get(get_redirect = True)
+ except wikipedia.NoPage:
+ wikipedia.output(u'Page %s not found.' % article.title())
+ return
+ try:
+ contextLength = full_text.index('\n\n')
+ except ValueError: # substring not found
+ contextLength = 500
+ if full_text.startswith(u'[['): # probably an image
+ # Add extra paragraph.
+ contextLength = full_text.find('\n\n', contextLength+2)
+ if contextLength > 1000 or contextLength < 0:
+ contextLength = 500
+ print
+ wikipedia.output(full_text[:contextLength])
+ print
+
+ subcatlist = self.catDB.getSubcats(current_cat)
+ supercatlist = self.catDB.getSupercats(current_cat)
+ print
+ if len(subcatlist) == 0:
+ print 'This category has no subcategories.'
+ print
+ if len(supercatlist) == 0:
+ print 'This category has no supercategories.'
+ print
+ # show subcategories as possible choices (with numbers)
+ for i in range(len(supercatlist)):
+ # layout: we don't expect a cat to have more than 10 supercats
+ wikipedia.output(u'u%d - Move up to %s' % (i, supercatlist[i].title()))
+ for i in range(len(subcatlist)):
+ # layout: we don't expect a cat to have more than 100 subcats
+ wikipedia.output(u'%2d - Move down to %s' % (i, subcatlist[i].title()))
+ print ' j - Jump to another category'
+ print ' s - Skip this article'
+ print ' r - Remove this category tag'
+ print ' ? - Print first part of the page (longer and longer)'
+ wikipedia.output(u'Enter - Save category as %s' % current_cat.title())
+
+ flag = False
+ while not flag:
+ print ''
+ choice=wikipedia.input(u'Choice:')
+ if choice in ['s', 'S']:
+ flag = True
+ elif choice == '':
+ wikipedia.output(u'Saving category as %s' % current_cat.title())
+ if current_cat == original_cat:
+ print 'No changes necessary.'
+ else:
+ catlib.change_category(article, original_cat, current_cat)
+ flag = True
+ elif choice in ['j', 'J']:
+ newCatTitle = wikipedia.input(u'Please enter the category the article should be moved to:')
+ newCat = catlib.Category(wikipedia.getSite(), 'Category:' + newCatTitle)
+ # recurse into chosen category
+ self.move_to_category(article, original_cat, newCat)
+ flag = True
+ elif choice in ['r', 'R']:
+ # remove the category tag
+ catlib.change_category(article, original_cat, None)
+ flag = True
+ elif choice == '?':
+ contextLength += 500
+ print
+ wikipedia.output(full_text[:contextLength])
+ print
+
+ # if categories possibly weren't visible, show them additionally
+ # (maybe this should always be shown?)
+ if len(full_text) > contextLength:
+ print ''
+ print 'Original categories: '
+ for cat in article.categories():
+ wikipedia.output(u'* %s' % cat.title())
+ elif choice[0] == 'u':
+ try:
+ choice=int(choice[1:])
+ except ValueError:
+ # user pressed an unknown command. Prompt him again.
+ continue
+ self.move_to_category(article, original_cat, supercatlist[choice])
+ flag = True
+ else:
+ try:
+ choice=int(choice)
+ except ValueError:
+ # user pressed an unknown command. Prompt him again.
+ continue
+ # recurse into subcategory
+ self.move_to_category(article, original_cat, subcatlist[choice])
+ flag = True
+
+ def run(self):
+ cat = catlib.Category(wikipedia.getSite(), 'Category:' + self.catTitle)
+
+ # get edit summary message
+ wikipedia.setAction(wikipedia.translate(wikipedia.getSite(), msg_change) % cat.title())
+
+ articles = cat.articlesList(recurse = False)
+ if len(articles) == 0:
+ wikipedia.output(u'There are no articles in category ' + catTitle)
+ else:
+ preloadingGen = pagegenerators.PreloadingGenerator(iter(articles))
+ for article in preloadingGen:
+ wikipedia.output(u'\n===================================================================')
+ self.move_to_category(article, cat, cat)
+
+class CategoryTreeRobot:
+ '''
+ Robot to create tree overviews of the category structure.
+
+ Parameters:
+ * catTitle - The category which will be the tree's root.
+ * catDB - A CategoryDatabase object
+ * maxDepth - The limit beyond which no subcategories will be listed.
+ This also guarantees that loops in the category structure
+ won't be a problem.
+ * filename - The textfile where the tree should be saved; None to print
+ the tree to stdout.
+ '''
+
+ def __init__(self, catTitle, catDB, filename = None, maxDepth = 10):
+ self.catTitle = catTitle
+ self.catDB = catDB
+ if filename and not os.path.isabs(filename):
+ filename = wikipedia.config.datafilepath(filename)
+ self.filename = filename
+ # TODO: make maxDepth changeable with a parameter or config file entry
+ self.maxDepth = maxDepth
+
+ def treeview(self, cat, currentDepth = 0, parent = None):
+ '''
+ Returns a multi-line string which contains a tree view of all subcategories
+ of cat, up to level maxDepth. Recursively calls itself.
+
+ Parameters:
+ * cat - the Category of the node we're currently opening
+ * currentDepth - the current level in the tree (for recursion)
+ * parent - the Category of the category we're coming from
+ '''
+
+ # Translations to say that the current category is in more categories than
+ # the one we're coming from
+ also_in_cats = {
+ 'ar': u'(أيضا في %s)',
+ 'ca': u'(també a %s)',
+ 'da': u'(også i %s)',
+ 'de': u'(auch in %s)',
+ 'en': u'(also in %s)',
+ 'es': u'(también en %s)',
+ 'fi': u'(myös luokassa %s)',
+ 'fr': u'(également dans %s)',
+ 'he': u'(גם בקטגוריות %s)',
+ 'ia': u'(equalmente in %s)',
+ 'is': u'(einnig í %s)',
+ 'kk': u'(тағы да %s дегенде)',
+ 'nds-nl': u'(oek in %s)',
+ 'nl': u'(ook in %s)',
+ 'no': u'(også i %s)',
+ 'nn': u'(òg i %s)',
+ 'pt': u'(também em %s)',
+ 'ru': u'(также в %s)',
+ 'sv': u'(också i %s)',
+ 'ср': u'(такође у %s)',
+ 'zh': u'(也在 %s)',
+ }
+
+ result = u'#' * currentDepth
+ result += '[[:%s|%s]]' % (cat.title(), cat.title().split(':', 1)[1])
+ result += ' (%d)' % len(self.catDB.getArticles(cat))
+ # We will remove an element of this array, but will need the original array
+ # later, so we create a shallow copy with [:]
+ supercats = self.catDB.getSupercats(cat)[:]
+ # Find out which other cats are supercats of the current cat
+ try:
+ supercats.remove(parent)
+ except:
+ pass
+ if supercats != []:
+ supercat_names = []
+ for i in range(len(supercats)):
+ # create a list of wiki links to the supercategories
+ supercat_names.append('[[:%s|%s]]' % (supercats[i].title(), supercats[i].title().split(':', 1)[1]))
+ # print this list, separated with commas, using translations given in also_in_cats
+ result += ' ' + wikipedia.translate(wikipedia.getSite(), also_in_cats) % ', '.join(supercat_names)
+ result += '\n'
+ if currentDepth < self.maxDepth:
+ for subcat in self.catDB.getSubcats(cat):
+ # recurse into subdirectories
+ result += self.treeview(subcat, currentDepth + 1, parent = cat)
+ else:
+ if self.catDB.getSubcats(cat) != []:
+ # show that there are more categories beyond the depth limit
+ result += '#' * (currentDepth + 1) + '[...]\n'
+ return result
+
+ def run(self):
+ """
+ Prints the multi-line string generated by treeview or saves it to a file.
+
+ Parameters:
+ * catTitle - the title of the category which will be the tree's root
+ * maxDepth - the limit beyond which no subcategories will be listed
+ """
+ cat = catlib.Category(wikipedia.getSite(), 'Category:' + self.catTitle)
+ tree = self.treeview(cat)
+ if self.filename:
+ wikipedia.output(u'Saving results in %s' % self.filename)
+ import codecs
+ f = codecs.open(self.filename, 'a', 'utf-8')
+ f.write(tree)
+ f.close()
+ else:
+ wikipedia.output(tree, toStdout = True)
+
+if __name__ == "__main__":
+ fromGiven = False
+ toGiven = False
+ batchMode = False
+ editSummary = ''
+ inPlace = False
+ overwrite = False
+ showImages = False
+ talkPages = False
+ recurse = False
+ titleRegex = None
+
+ # This factory is responsible for processing command line arguments
+ # that are also used by other scripts and that determine on which pages
+ # to work on.
+ genFactory = pagegenerators.GeneratorFactory()
+ # The generator gives the pages that should be worked upon.
+ gen = None
+
+ #If this is set to true then the custom edit summary given for removing
+ #categories from articles will also be used as the deletion reason.
+ useSummaryForDeletion = True
+ try:
+ catDB = CategoryDatabase()
+ action = None
+ sort_by_last_name = False
+ restore = False
+ for arg in wikipedia.handleArgs():
+ if arg == 'add':
+ action = 'add'
+ elif arg == 'remove':
+ action = 'remove'
+ elif arg == 'move':
+ action = 'move'
+ elif arg == 'tidy':
+ action = 'tidy'
+ elif arg == 'tree':
+ action = 'tree'
+ elif arg == 'listify':
+ action = 'listify'
+ elif arg == '-person':
+ sort_by_last_name = True
+ elif arg == '-rebuild':
+ catDB.rebuild()
+ elif arg.startswith('-from:'):
+ oldCatTitle = arg[len('-from:'):].replace('_', ' ')
+ fromGiven = True
+ elif arg.startswith('-to:'):
+ newCatTitle = arg[len('-to:'):].replace('_', ' ')
+ toGiven = True
+ elif arg == '-batch':
+ batchMode = True
+ elif arg == '-inplace':
+ inPlace = True
+ elif arg == '-delsum':
+ # This parameter is kept for historical reasons, as it was not previously the default option.
+ pass
+ elif arg == '-nodelsum':
+ useSummaryForDeletion = False
+ elif arg == '-overwrite':
+ overwrite = True
+ elif arg == '-showimages':
+ showImages = True
+ elif arg.startswith('-summary:'):
+ editSummary = arg[len('-summary:'):]
+ elif arg.startswith('-match'):
+ if len(arg) == len('-match'):
+ titleRegex = wikipedia.input(u'Which regular expression should affected objects match?')
+ else:
+ titleRegex = arg[len('-match:'):]
+ elif arg == '-talkpages':
+ talkPages = True
+ elif arg == '-recurse':
+ recurse = True
+ else:
+ gen = genFactory.handleArg(arg)
+
+ if action == 'add':
+ if not gen:
+ gen = genFactory.handleArg('-links') #default for backwords compatibility
+ # The preloading generator is responsible for downloading multiple
+ # pages from the wiki simultaneously.
+ gen = pagegenerators.PreloadingGenerator(gen)
+ add_category(sort_by_last_name)
+ elif action == 'remove':
+ if (fromGiven == False):
+ oldCatTitle = wikipedia.input(u'Please enter the name of the category that should be removed:')
+ bot = CategoryRemoveRobot(oldCatTitle, batchMode, editSummary, useSummaryForDeletion, inPlace = inPlace)
+ bot.run()
+ elif action == 'move':
+ if (fromGiven == False):
+ oldCatTitle = wikipedia.input(u'Please enter the old name of the category:')
+ if (toGiven == False):
+ newCatTitle = wikipedia.input(u'Please enter the new name of the category:')
+ bot = CategoryMoveRobot(oldCatTitle, newCatTitle, batchMode, editSummary, inPlace, titleRegex = titleRegex)
+ bot.run()
+ elif action == 'tidy':
+ catTitle = wikipedia.input(u'Which category do you want to tidy up?')
+ bot = CategoryTidyRobot(catTitle, catDB)
+ bot.run()
+ elif action == 'tree':
+ catTitle = wikipedia.input(u'For which category do you want to create a tree view?')
+ filename = wikipedia.input(u'Please enter the name of the file where the tree should be saved, or press enter to simply show the tree:')
+ bot = CategoryTreeRobot(catTitle, catDB, filename)
+ bot.run()
+ elif action == 'listify':
+ if (fromGiven == False):
+ oldCatTitle = wikipedia.input(u'Please enter the name of the category to listify:')
+ if (toGiven == False):
+ newCatTitle = wikipedia.input(u'Please enter the name of the list to create:')
+ bot = CategoryListifyRobot(oldCatTitle, newCatTitle, editSummary, overwrite, showImages, subCats = True, talkPages = talkPages, recurse = recurse)
+ bot.run()
+ else:
+ wikipedia.showHelp('category')
+ finally:
+ catDB.dump()
+ wikipedia.stopme()