Difference between revisions of "User:WindBOT/Filters"

From Team Fortress Wiki
Jump to: navigation, search
m (+)
(disable Russian quote filters until Wind gets online to fix it)
Line 312: Line 312:
  
 
=== Russian filters ===
 
=== Russian filters ===
   addSafeFilter(
+
   """addSafeFilter(
 
       regexes({
 
       regexes({
 
           (u'^([^<>]*)([""])((?:(?!\\2).)+)\\2([^<>]*)$', re.MULTILINE): u'$1«$3»$4',
 
           (u'^([^<>]*)([""])((?:(?!\\2).)+)\\2([^<>]*)$', re.MULTILINE): u'$1«$3»$4',
Line 318: Line 318:
 
       }),
 
       }),
 
       language='ru'
 
       language='ru'
   )
+
   )"""
  
 
== Link filters ==
 
== Link filters ==

Revision as of 08:56, 14 June 2011

How to disable a filter

If the bot is malfunctioning, chances are that the problem lies in one of these blocks of code. Thus, instead of shutting down the whole bot, it would be wiser to disable only the chunk of code that is misbehaving. To make the bot ignore a certain line, add a "#" in front of it:

 # This line will be ignored

If there are multiple lines, wrap them inside triple-quotes (you still need to put the two spaces at the beginning of the line):

 """This line will be ignored
 and this one as well
 and this one is cake
 and the previous one was a lie but it was still ignored"""

If all else fails, you can simply delete the block from the page. The bot can't come up with code by itself yet, so it won't run anything. Or, if the problem really is elsewhere, block the bot.

Global filters

File categorization (Add {{No category}})

 def fileCategorization(content, **kwargs):
 	badLoneCategories = [u'Category:Screenshot images', u'Category:Artwork images', u'Category:Multimedia files', u'Category:Extracted media', u'Category:Images without license tags', u'Category:Images that need improving', u'Category:Images using ((Another license)) incorrectly']
 	extraStuff = u'{{No category}}'
 	regLang = compileRegex('/[^/]+$')
 	if 'article' not in kwargs or content.find(u'#REDIRECT ') != -1:
 		return content
 	title = u(kwargs['article'].title)
 	if title[:5].lower() != u'file:':
 		return content
 	for c in kwargs['article'].getCategories():
 		if u(regLang.sub(u'', c)) not in badLoneCategories:
 			return content
 	if not len(content):
 		return extraStuff
 	if content.find(extraStuff) != -1:
 		return content
 	return content.strip() + u'\n' + extraStuff
 addFilter(fileCategorization)

Page filters

 addPageFilter(r'^user:', r'(?:talk|help|wiki|template):')

Semantic filters

Item names

 categories = [ # These are categories that contain that name of things that should be capitalized
     'Weapons',
     'Hats',
     'Miscellaneous items',
     'Buildings'
 ]
 exceptions = [ # Those are page names that are in the above categories but should not count as capitalized weapon names
     'Direct Hit',
     'Hats', 'Weapons', 'Buildings',
     'Item levels',
     'Developer weapons',
     'Self-made items',
     'Unused content',
     'Promotional items',
     'Mercenary', 'Taunts', 'Decapitation', 'Fists', # Too common to be reliably replaced
     'Club', 'Bat', 'Knife', 'Syringe', 'Bottle', # Ditto (weapons)
     'Classified', # Not capitalized even when referring to the hat
     'Attendant', # Avoids issue with French
     'Buff Banner', 'Medi Gun', 'Bonk Helm' # Avoids issue with German
 ]
 for c in categories:
     c = wikitools.category.Category(wiki(), u(c))
     for p in pageFilter(c.getAllMembers(titleonly=True)):
         if p not in exceptions:
             enforceCapitalization(p)
 # Special case: Direct Hit
 addSafeFilter(wordFilter('Direct Hit', r'(?<!a )(?<!one )(?<!on )(?<!\()(?<!placed )(?<!single )\bDirect Hit'))
 # Special case: Attendant
 enforceCapitalization('Attendant', languageBlacklist=['fr'])
 # Special cases: "Buff-Banner"/"Medigun" in German
 enforceCapitalization('Buff Banner', 'Medi Gun', languageBlacklist=['de'])
 enforceCapitalization('Buff-Banner', 'Medigun', language='de')
 # Put "an" in front of Electro Sapper
 addSafeFilter(
     dumbReplace('a Electro Sapper', 'an Electro Sapper'),
     dumbReplace('A Electro Sapper', 'An Electro Sapper')
 )
 # Flare Gun
 addSafeFilter(wordFilter('Flare Gun', 'flaregun'))

Classes

 # Not "Heavy" because the word can be used as an adjective:
 classes = ['Scout', 'Soldier', 'Pyro', 'Demoman', 'Heavy Weapons Guy', 'Engineer', 'Medic', 'Sniper'] # Spy added later
 classesPlurals = ['Scouts', 'Pyros', 'Demomen', 'Heavy Weapons Guys', 'Heavies', 'Engineers', 'Snipers', 'Spies']
 enforceCapitalization(*classes)
 enforceCapitalization(*classesPlurals)
 # Special Spy case:
 addSafeFilter(wordFilter('Spy', '(?<!I )Spy'))
 classes.append('Spy') # Used in later functions with Spy included

BLU/RED

DISABLED because of TFC's team names

 """addSafeFilter(wordFilter('BLU', 'BLU(?![- ]+ray)'))
 # Can't force simply "red" because it would ruin things like "this red hat" or something. However, the following can probably be assumed to be correct:
 for c in classes + classesPlurals + ['Soldiers']:
     enforceCapitalization('RED ' + c) # Capitalize "RED Scout", "RED Demoman", etc
 enforceCapitalization('RED team', 'RED Heavy', 'RED Intelligence')
 # Similarly, "blue" can be converted to "BLU" in the same cases:
 for c in classes + classesPlurals + ['Soldiers']:
     addSafeFilter(wordFilter('BLU ' + c, 'blue? ' + c)) # Convert "blue scout" -> "BLU Scout", "blue demoman" -> "BLU Demoman", etc.
 addSafeFilter(
     wordFilter('BLU team', 'blue? team'),
     wordFilter('BLU Heavy', 'blue? Heavy'),
     wordFilter('BLU Intelligence', 'blue? intelligence')
 )"""

Other capitalized words

 enforceCapitalization('Team Fortress 2', 'TF2')
 enforceCapitalization('Payload', 'Cloak', 'Overtime', 'Dispensers', 'Sappers')
 enforceCapitalization('PlayStation', 'Xbox')
 enforceCapitalization('iPod', 'iPhone')

Word aliases

 addSafeFilter(
     wordFilter(u'Übersaw', u'[üu]bersaw'), # Ubersaw
     wordFilter(u'Force-a-Nature', u'Force of Nature'), # Force-A-Nature
     wordFilter(u'ÜberCharge', u'[Üüu]ber(?!säge)(?!sage)(?: ?charge)?'), # ÜberCharge
     wordFilter(u'ÜberCharges', u'[Üüu]ber(?!säge)(?!sage)(?! Entertainment)(?: ?charge)?s'), # ÜberCharges
     wordFilter(u'Intelligence', u'Intel(?!\\s*(?:CPU|processor))'), # Intelligence (maybe adding "flag" in there would be too agressive)
     wordFilter(u'Electro Sapper', u'(?<!Elektro.)(?<!Electro.)(?<!Ultra.)Sapper', u'Electro-?Sapper'),
     wordFilter(u'Heavy Weapons Guy', u'Heavy Guy'),
     wordFilter(u'Intelligence room', u'Intel(?:ligence)? room'),
     wordFilter(u'Intelligence briefcase', u'Intel(?:ligence)? briefcase'),
     wordFilter(u'K.G.B.', u'KGB'),
     wordFilter(u'Chieftain', u'Chieftian', u'Chieftan'),
     wordFilter(u'Batallion\'s Backup', u'Batallion\'?s? Back-?up'),
     wordFilter(u'Mann-Conomy', u'Mann?-?Conomy'),
     wordFilter(u'First-person view', u'1(?:st)? person view')
     #wordFilter(u'Stickybomb', u'Stickybomb', u'Sticky bomb'),
     #wordFilter(u'Stickybombs', u'Stickybombs', u'Sticky bombs')
 )
 addSafeFilter(
     wordFilter(u'Medi Gun', u'Medi-?Gun'),
     languageBlacklist=['pl', 'de']
 )

Sentry Gun

 addSafeFilter(
     wordFilter(u'Sentry Gun', r"(?<!Mini.)(?<!Mah.)(?<!My.)Sentry(?![-\s]*(?:Gun|here|ahead|forward|up there|right up|, right up|'{2,}|jump)|-)"),
     wordFilter(u'Sentry Guns', r'Sentry Guns', r'Sentries(?!\s+Gun)'),
     wordFilter(u'Combat Mini-Sentry Gun', r'Combat Mini-Sentry Gun', r'(?:(?:Mini|Combat)\s+)+Sentry(?!\s+Gun|-)'),
     wordFilter(u'Combat Mini-Sentry Guns', r'(?:(?:Mini|Combat)[-\s]+)+Sentr(?:y[-\s]+Guns|ies)')
 )
 addFilter(regex(r'\[\[([^][|]+\|)?(?:Sentry Guns|Sentries)\]\]', '$1Sentry Guns')) # Put the "s" out of the link

Common misspellings

 addSafeFilter(
     wordFilter('Huntsman', 'Hunstman'),
     wordFilter('Dispenser', 'Dis?pen[sc][eo]r'),
     wordFilter('Heavy', 'Hevy'),
     wordFilter('Engineer', 'Enginer'),
     wordFilter('Soldier', 'Solider'),
     wordFilter('Mini-Crit', 'Minicrit'),
     wordFilter('Mini-Crits', 'Minicrits'),
     wordFilter('Chargin\' Targe', 'charg[ei][-\'n\\s]*targe?'),
     wordFilter('Sasha', 'Sa[sch]+a'),
     wordFilter('Kritzkrieg', 'Kritzkreig'),
     wordFilter('screenshot', 'screen shot'),
     wordFilter('screenshots', 'screen shots'),
     wordFilter('in-game', 'ingame'),
     wordFilter('team-colored', 'team colou?red'),
     wordFilter('color', '(?<!Rustic )colour')
 )
 addSafeFilter(
     wordFilter('Natascha', 'Natas?c?ha'),
     language='en'
 )
 addSafeFilter(
     wordFilter('Spies', 'Spys'),
     languageBlacklist=['de', 'es']
 )

Map names

 addSafeFilter(
     wordFilter('Gravel Pit', '(?<!cp_)Gravelpit', 'Gravel pit'),
     wordFilter('Double Cross', 'Double Cross', '(?<!ctf_)Doublecross'),
     wordFilter('Badwater Basin', '(?<!pl_)Badwater Basin', '(?<!pl_)Badwater(?!\s+Basin)'),
     wordFilter('Gold Rush', '(?<!pl_)Goldrush')
 )
 # Capitalisation rules for map names are whitelist-based rather than Category:Maps+blacklist based, because lots of maps are common nouns.
 # As such, if a map with a non-common name is added, please add it to this list.
 enforceCapitalization(
     '2Fort', 'Badlands', 'Coldfront', 'Dustbowl', 'Egypt', 'Fastlane', 'Granary', 'Gullywash',
     'Hightower', 'Hoodoo', 'Landfall', 'Offblast', 'Thunder Mountain',
     'Viaduct', 'Wildfire', 'Yukon'
 )

Section headers

 addSafeFilter(
     wordFilter(u'== Update history ==', u'==+ ?(?:Update history|Previous changes) ?==+')
 )
 enforceCapitalization('== See also ==', '== External links ==', '== Painted variants ==', '== Item set ==', '== Damage and function times ==')

Language-specific filters

 # Language-agnostic achievements auto-translate
 def translateAchievements(language, pageSuffix):
     try:
         tf = readLocaleFile(urllib2.urlopen(page('File:Tf_' + language + '.txt').getDownloadUrl()).read(-1))
     except:
         print 'Downloading failed.'
         return
     try:
         languages = parseLocaleFile(tf, language=language)
     except:
         print 'Error while parsing tf_' + language + '.txt'
         return
     acceptPrefix = ['TF_SCOUT_', 'TF_SOLDIER_', 'TF_PYRO_', 'TF_DEMOMAN_', 'TF_HEAVY_', 'TF_ENGINEER_', 'TF_MEDIC_', 'TF_SNIPER_', 'TF_SPY_', 'TF_GET_', 'TF_KILL_', 'TF_BURN_', 'TF_WIN_', 'TF_PLAY_']
     acceptSuffix = ['_NAME', '_DESC']
     filteredAchievements = languagesFilter(languages, commonto=[language, 'english'], prefix=acceptPrefix, suffix=acceptSuffix, exceptions=['TF_SOLDIER_ASSIST_MEDIC_UBER_NAME'])
     associateLocaleWordFilters(filteredAchievements, 'english', language, pageSuffix)

Romanian filters

 # Romanian characters
 addSafeFilter(
     dumbReplaces({
         u'ş': u'ș',
         u'ţ': u'ț'
     }), language='ro'
 )
 # Romanian achievements (disabled for now)
 #translateAchievements('romanian', 'ro')
 # Fix User:Vulturas's stoopid:
 addSafeFilter(
     wordFilter(u'batjocură', u'batjocoră'),
     wordFilter(u'batjocura', u'batjocora'),
     wordFilter(u'batjocureşte', u'batjocoreşte', u'batjocorește'),
     wordFilter(u'batjocuri', u'batjocori'),
     wordFilter(u'batjocurile', u'batjocorile'),
     wordFilter(u'batjocurii', u'batjocorii'),
     language='ro'
 )

German filters

 addSafeFilter(
     wordFilter('Krit-\'n-Cola', '[CK]rit-\'?[an]\'?-Cola'),
     wordFilter('Krit', 'Crit'),
     wordFilter('Krits', 'Crits'),
     language='de'
 )
 # German achievements (disabled for now)
 #translateAchievements('german', 'de')
 addSafeFilter(
     wordFilter(u'== Update-Verlauf ==', u'==+ *(?:Update Verlauf|Letzte [Ääa]nderungen) *==+'), language='de'
 )

Spanish filters

 addSafeFilter(
     wordFilter(u'Forajido', u'Gunslinger'),
     wordFilter(u'Pistolón', u'Gran Masacre'),
     wordFilter(u'Medic', u'M[eé]dico(?! Medieval)'),
     wordFilter(u'Medics', u'M[eé]dicos(?! Medieval)'),
     wordFilter(u'Sniper', u'Francotirador'),
     wordFilter(u'Snipers', u'Francotiradore?s'),
     wordFilter(u'Spy', u'Esp[ií]a'),
     wordFilter(u'Spies', u'Spys', u'Esp[ií]as'),
     wordFilter(u'Soldier', u'Soldado(?! de Fortuna)'),
     wordFilter(u'Soldiers', u'Soldados'),
     wordFilter(u'Engineer', u'(?<!Gorra de )Ingeniero'),
     wordFilter(u'Engineers', u'Ingenieros'),
     wordFilter(u'Curiosidades', u'Trivia'),
     wordFilter(u'Actualizacion Mann-Conomy', u'Mann-Conomy Update'),
     wordFilter(u'Nivel', u'Level'),
     wordFilter(u'Cuerpo a cuerpo', u'Melee'),
     language='es'
 )
 class spanishDateFilter: # Requested by BiBi
     def __init__(self):
         self.reg = compileRegex(r'(?:Parche|Patch) del? (\d{1,2}) (?:del?)? (Enero|Febrero|Marzo|Abril|Mayo|Ju[nl]io|Agosto|Septiembre|Oct[ou]bre|Noviembre|Diciembre) de (\d{4})')
         self.filterName = u'Spanish date consistency filter'
     def replace(self, match):
         return u'Parche del ' + u(match.group(1)) + u' de ' + u(match.group(2)).title() + u' de ' + u(match.group(3))
     def __call__(self, content, **kwargs):
         return self.reg.sub(self.replace, content)
 addSafeFilter(spanishDateFilter(), language='es')
 addSafeFilter( # Requested by Dio
     wordFilter(u'== Variaciones de Colores ==', u'==+ *Variantes +pintadas *==+'), language='es'
 )
 addSafeFilter( # Requested by Dio
     wordFilter(u'La trampa', u'El cheto', keepcapitalization=True),
     wordFilter(u'Las trampas', u'Los chetos', keepcapitalization=True),
     wordFilter(u'Trampas', u'Chetos', keepcapitalization=True),
     wordFilter(u'Trampa', u'Cheto', keepcapitalization=True),
     language='es'
 )

French filters

 enforceCapitalization(
     u'janvier', u'février', u'mars', u'avril', u'mai', u'juin', u'juillet', u'août', u'septembre', u'octobre', u'novembre', u'décembre', language='fr'
 ) # Do not capitalize months
 addSafeFilter(wordFilter('janvier', 'janviers'), language='fr')
 addSafeFilter(
     wordFilter(u'== Historique des mises à jour ==', u'==+ *(?:Historique des? mises? [aà] jour|Changements? pr.c.dents?) *==+'), language='fr'
 )

Brazilian Portuguese filters

 def portugueseInfoboxAvailabilityFilter(t, **kwargs): # Requested by Ohyeahcruz
     if t.getName().lower() == u'item infobox':
         return t # Skip
     set.setParam('availability', t.getParam('availability').replace(u'craft', u'fabricação').replace(u'Craft', u'Fabricação'))
     return t
 addTemplateFilter(portugueseInfoboxAvailabilityFilter, language='pt-br')
 addSafeFilter( # Requested by Ohyeahcruz
     wordFilter(u'Fabricação', u'Crafting', keepcapitalization=True),
     wordFilter(u'Conquista', u'Achievement', keepcapitalization=True),
     wordFilter(u'Conquistas', u'Achievements', keepcapitalization=True),
     language='pt-br'
 )

Russian filters

 """addSafeFilter(
     regexes({
         (u'^([^<>]*)([""])((?:(?!\\2).)+)\\2([^<>]*)$', re.MULTILINE): u'$1«$3»$4',
         u'«([^»]*)(?<!\')\'([^»\']+)\'(?!\')': u'«$1„$2“'
     }),
     language='ru'
 )"""

Link filters

Wikipedia links filter

 def wikipediaLinks(link, **kwargs):
     wikipediaRegex = compileRegex(r'^https?://(?:(\w+)\.)?wikipedia\.org/wiki/(\S+)')
     if link.getType() == u'external':
         linkInfo = wikipediaRegex.search(link.getLink())
         if linkInfo:
             link.setType(u'internal')
             try:
                 wikiPage = urllib2.unquote(str(linkInfo.group(2))).decode('utf8', 'ignore').replace(u'_', ' ')
             except:
                 wikiPage = u(linkInfo.group(2)).replace(u'_', ' ')
             if not linkInfo.group(1) or linkInfo.group(1).lower() == u'en':
                 link.setLink(u'Wikipedia:' + wikiPage) # English Wikipedia
             else:
                 link.setLink(u'Wikipedia:' + linkInfo.group(1).lower() + u':' + wikiPage) # Non-english Wikipedia
             if link.getLabel() is None:
                 link.setLabel(u'(Wikipedia)')
     return link
 addLinkFilter(wikipediaLinks)

TF2Wiki links filter

 def tf2wikiLinks(link, **kwargs):
     tf2wikiRegex1 = compileRegex(r'^https?://[-.\w]*tf2wiki\.net/wiki/(\S+)$')
     tf2wikiRegex2 = compileRegex(r'^https?://[-.\w]*tf2wiki\.net/w[-_/\w]+?/([^/\s]+)$')
     if link.getType() == 'external':
         linkInfo = tf2wikiRegex1.search(link.getLink())
         isMedia = False
         if not linkInfo:
             linkInfo = tf2wikiRegex2.search(link.getLink())
             isMedia = True
         if linkInfo:
             link.setType('internal')
             try:
                 wikiPage = u(urllib2.unquote(str(linkInfo.group(1))).decode('utf8', 'ignore').replace(u'_', ' '))
             except:
                 wikiPage = u(linkInfo.group(1)).replace(u'_', ' ')
             label = wikiPage
             if isMedia:
                 if wikiPage[-4:].lower() == '.wav':
                     wikiPage = 'Media:' + wikiPage
                 else:
                     wikiPage = ':File:' + wikiPage
             link.setLink(wikiPage)
             if link.getLabel() is None:
                 link.setLabel(label)
     return link
 addLinkFilter(tf2wikiLinks)

Category removal on pages using {{Item infobox}}

 def removeCategory(l, **kwargs):
     catsToRemove = [u'Category:Weapons', u'Category:Hats', u'Category:Primary weapons', u'Category:Secondary weapons', u'Category:Melee weapons', u'Category:PDA1 weapons', u'Category:PDA2 weapons', u'Category:Miscellaneous items', u'Category:Tools', u'Category:Action items', u'Category:Taunts', u'Community-contributed items']
     regLang = compileRegex('/[^/]+$')
     if 'article' not in kwargs or regLang.sub(u, l.getLink()) not in catsToRemove:
         return l
     if u'Category:Item infobox usage' not in kwargs['article'].getCategories():
         return l
     return None
 addLinkFilter(removeCategory)

FPSBanana to GameBanana

 addLinkFilter(linkDomainFilter('fpsbanana.com', 'gamebanana.com'))

Remove trailing slashes from internal links

 def removeTrailingSlash(l, **kwargs):
     if l.getType() != u'internal':
         return l
     if l.getLink()[-1] == '/':
         l.setLink(l.getLink()[:-1])
     return l
 addLinkFilter(removeTrailingSlash)

Template filters

Template renaming

 def templateRenameMapping(t, **kwargs):
     templateMap = {
         # Format goes like this (without the "#" in front obviously):
         #'Good template name': ['Bad template lowercase name 1', 'Bad template lowercase name 2', 'Bad template lowercase name 3'],
         # Last line has no comma at the end
         'Scout Nav': ['scout nav/ro'],
         'Soldier Nav': ['soldier nav/ro'],
         'Pyro Nav': ['pyro nav/ro'],
         'Heavy Nav': ['heavy nav/ro'],
         'Demoman Nav': ['demoman nav/ro'],
         'Engineer Nav': ['engineer nav/ro'],
         'Medic Nav': ['medic nav/ro'],
         'Sniper Nav': ['sniper nav/ro'],
         'Spy Nav': ['spy nav/ro'],
         'Promo nav': ['mncnav', 'pokernightnav', 'l4dnav', 'kfnav'],
         'Crush': ['pngcrush']
     }
     for n in templateMap:
         if t.getName().lower() in templateMap[n]:
             t.setName(n)
     return t
 addTemplateFilter(templateRenameMapping)

Manage all {{Item infobox}}es

 def infoboxFilter(t, **kwargs):
     filteredTemplates = ('weapon infobox', 'hat infobox', 'tool infobox', 'item infobox') # Only do stuff to these templates
     if t.getName().lower() not in filteredTemplates:
         return t # Skip
     t.setName('Item infobox')
     t.indentationMatters(True) # Reindents every time, not only when modifying values
     paramAliases = { # Parameter alias 'goodParam': 'badParam', or 'goodParam': [list of bad params].
         'name': ['weapon-name-override', 'hat-name-override', 'tool-name-override', 'NAME', 'name-override'],
         'image': ['weapon-image', 'hat-image', 'tool-image'],
         'kill-text-1': 'kill-text',
         'team-colors': 'has-team-colors',
         'two-models': 'has-two-models',
         'slot': ['weapon-slot', 'hat-slot', 'tool-slot'],
         'trade': 'tradable',
         'gift': 'giftable',
         'craft': 'craftable',
         'paint': ['paintable', 'Paint'],
         'rename': ['name-tag', 'nametag'],
         'loadout': 'display-loadout-stats',
         'level': 'level-and-type'
     }
     catstoCheck = { # Mapping 'templateAttribute': [List of 'Category|templateAttributeValue']
         'type': ['Taunts|taunt', 'Action items|action', 'Hats|hat', 'Miscellaneous items|misc item', 'Tools|tools', 'Weapons|weapon'],
         'slot': ['Primary weapons|primary', 'Secondary weapons|secondary', 'Melee weapons|melee', 'PDA1 weapons|pda 1', 'PDA2 weapons|pda 2']
     }
     catsDontCheck = [u'Category:Beta and unused content', 'Category:Taunts'] # Type and slot won't be modified on these pages
     preferedOrder = [ # Prefered order of keys inside template
         'name', 'game', 'type', 'beta', 'unused', 'image', 'imagewidth', 'team-colors', 'two-models', 'skin-image-red', 'skin-image-blu', 'TFC-model', 'QTF-model', 'kill-icon-#', 'kill-text-#', 'used-by', 'slot', 'crafting-slot', 'custom-slot', 'contributed-by', 'released', 'availability', 'trade', 'gift', 'craft', 'numbered', 'paint', 'rename', 'medieval', 'ammo-loaded', 'ammo-carried', 'ammo-type', 'show-ammo', 'reload', 'loadout', 'loadout-prefix', 'quality', 'level', '%ATTRIBUTES%', 'item-description', 'item-uses', 'item-flags', 'item-expiration'
     ]
     checkEnglish = ['trade', 'craft', 'paint', 'rename'] # If these attributes aren't yes or no, check the values on the english page
     attributeTypes = ['neutral', 'positive', 'negative'] # Possible loadout attribute types
     regLang = compileRegex('/[^/]+$')
     # Step 0 - Check categories:
     tCats = None
     isTFC = False # Assume false by default
     if 'article' in kwargs:
         if kwargs['article'] is not None:
             cats2 = kwargs['article'].getCategories()
             tCats = []
             for c in cats2:
                 tCats.append(u(regLang.sub(u, u(c))))
             isTFC = u'Category:Weapons (Classic)' in tCats
     # Step 1 - Rename obsolete attributes
     for p in paramAliases:
         if type(paramAliases[p]) is type([]):
             for a in paramAliases[p]:
                 t.renameParam(a, p)
         else:
             t.renameParam(paramAliases[p], p)
     # Step 2 - Fix ammo stuff
     if t.getParam('ammo-carried') == u'1' or t.getParam('ammo-loaded') == u'1':
         t.delParam('ammo-loaded', 'ammo-carried', 'ammo-type')
         t.setParam('show-ammo', 'off')
     # Step 3 - Fix more ammo stuff, delete pricing attributes
     t.delParam('ammo', 'price', 'show-price', 'purchasable', 'backpack-image')
     # Step 4 - Fix reload stuff
     if t.getParam('reload') is None:
         t.renameParam('reload-type', 'reload')
     # Step 5 - Count, split, order and fix loadout attributes
     attrNumber = 1
     regexAttrSplit = compileRegex(r'\s*<br[^<>]*>\s*')
     for a in attributeTypes:
         if t.getParam(a + '-attributes') is not None:
             attrs = regexAttrSplit.split(t.getParam(a + '-attributes'))
             for attr in attrs:
                 t.setParam('att-' + str(attrNumber) + '-' + a, attr)
                 attrNumber += 1
             t.delParam(a + '-attributes')
     if tCats is not None:
         # Step 6 - Lookup english fallback on certain attributes
         fetchEnglish = False
         values = {}
         for attr in checkEnglish:
             if t.getParam(attr) is not None and t.getParam(attr).lower() not in (u'yes', u'no'):
                 fetchEnglish = True
             elif t.getParam(attr) is not None:
                 values[attr] = t.getParam(attr).lower()
         if regLang.search(kwargs['article'].title):
             englishArticle = page(regLang.sub(, kwargs['article'].title))
             try:
                 englishContent = englishArticle.getWikiText()
             except:
                 englishContent = u
             englishContent, englishTemplates = templateExtract(englishContent)
             for englishT in englishTemplates:
                 if englishT.getName().lower() in filteredTemplates:
                     for p in paramAliases:
                         if type(paramAliases[p]) is type([]):
                             for a in paramAliases[p]:
                                 englishT.renameParam(a, p)
                         else:
                             englishT.renameParam(paramAliases[p], p)
                     for attr in checkEnglish:
                         if englishT.getParam(attr) is not None and englishT.getParam(attr).lower() in (u'yes', u'no'):
                             values[attr] = englishT.getParam(attr).lower()
                     break
         for attr in values:
             t.setParam(attr, values[attr])
         checkCats = True
         for c in catsDontCheck:
             if c in tCats:
                 checkCats = False
                 break
         if not isTFC and not t.getParam('custom-slot') and checkCats:
             # Step 7 - Set certains attributes based on page categories
             for cname in catstoCheck:
                 cname = u(cname)
                 found = None
                 foundmultiple = False
                 for c in catstoCheck[cname]:
                     cat, val = u(c).split(u'|')
                     cat = u'Category:' + u(regLang.sub(u, cat))
                     if cat in tCats:
                         if found is not None:
                             foundmultiple = True
                         found = val
                 if not foundmultiple:
                     t.setParam(cname, found)
             if u'Category:Weapons' not in tCats:
                 t.delParam('slot')
             # Step 8 - Convert neutral attributes - Disabled
             """desc = []
             if t.getParam('item-description') is not None:
                 desc = regexAttrSplit.split(t.getParam('item-description'))
             for i in range(1, 10):
                 if t.getParam('att-' + str(i) + '-neutral') is not None:
                     desc.append(t.getParam('att-' + str(i) + '-neutral'))
                     t.delParam('att-' + str(i) + '-neutral')
             desc2 = []
             for d in desc:
                 if d.strip():
                     desc2.append(d.strip())
             if len(desc2):
                 t.setParam('item-description', u'
'.join(desc2))""" # # Commenting out because of new parameter 'beta' # and there being no way to distinguish between beta items & unused content thus far # seb26 04:58, 12 February 2011 (UTC) # # if u'Category:Beta and unused content' in tCats: # t.setParam('unused', 'yes') # Step 9 - Do TFC stuff if isTFC: t.setParam('type', 'weapon') t.setParam('game', 'tfc') # Step 10 - Set correct preferred indentation for k in ['quality', 'level', 'item-description', 'item-uses', 'item-flags', 'level-and-type', 'loadout-name', 'loadout-prefix']: t.setPreferedIndentation(k, 2) for i in range(1, 10): for a in attributeTypes: t.setPreferedIndentation('att-' + str(i) + '-' + a, 2) # Step 11 - Build correct attribute order newOrder = [] for o in preferedOrder: if o.find('#') == -1: newOrder.append(o) continue if o == '%ATTRIBUTES%': for i in range(1, 10): for a in attributeTypes: newOrder.append('att-' + str(i) + '-' + a) for i in range(1, 10): newOrder.append(o.replace('#', str(i))) t.setPreferedOrder(newOrder) # Step 12 - There is no step 12 return t addTemplateFilter(infoboxFilter)

Remove useless templates

 def removeUselessTemplate(t, **kwargs):
     if t.getName().lower() in (u'targeted', u'languages'):
         return None # Delete template
     return t
 addTemplateFilter(removeUselessTemplate)

Remove manual video IDs from {{Weapon Demonstration}}

 def weaponDemonstrationFilter(t, **kwargs):
     if t.getName().lower() != 'weapon demonstration':
         return t # Skip
     t.delParam('1')
     return t
 addTemplateFilter(weaponDemonstrationFilter)

Filter parameters of certain templates

 def templateParamFilter(t, **kwargs):
     params = { # Map: 'lowercase template name': ['list', 'of', 'params', 'to', 'filter']
         'patch layout': ['before', 'after', 'current'],
         'item infobox': ['released']
     }
     if t.getName().lower() not in params:
         return t
     for p in params[t.getName().lower()]:
         if t.getParam(p):
             t.setParam(p, fixContent(t.getParam(p), **kwargs))
     return t
 addTemplateFilter(templateParamFilter)

Remove obsolete parameters

 def obsoleteParameterFilter(t, **kwargs):
     params = { # Map: 'lowercase template name': ['list', 'of', 'params', 'to', 'delete']
         'blueprint': ['ingredient-#n-local', 'result-local'],
         'taunt': ['weapon-#n-local'],
     }
     if t.getName().lower() not in params:
         return t
     for p in params[t.getName().lower()]:
         p = u(p)
         if p.find(u'#n') != -1:
             for i in range(10):
                 t.delParam(p.replace(u'#n', str(i)))
         else:
             t.delParam(p)
     return t
 addTemplateFilter(obsoleteParameterFilter)

Fix map infobox attribute

 def mapTypeFix(t, **kwargs):
     mapTypeDict = {
          'Arena': ['Badlands (Arena)', 'Granary (Arena)', 'Hardhat', 'Lumberyard', 'Nucleus', 'Offblast', 'Ravine', 'Sawmill', 'Watchtower', 'Well (Arena)'],
          'Capture the Flag': ['2Fort', 'Atrophy (Capture the Flag)', 'Bedrock', 'Cloudburst', 'Converge', 'Convoy', 'Deliverance', 'Double Cross', 'Fusion', 'HAARP', 'Landfall', 'Mercy', 'Overlook', 'Premuda', 'Sawmill (Capture the Flag)', 'Slate (Capture the Flag)', 'Stockpile', 'Turbine', 'Vector', 'Well (Capture the Flag)', 'Wildfire', 'Wiretap'],
          'Control Point': ['5Gorge', 'Badlands', 'Coldfront', 'DeGroot Keep', 'Dustbowl', 'Egypt', 'Fastlane', 'Freight', 'Furnace Creek', 'Glacier', 'Gorge', 'Granary', 'Gravel Pit', 'Gullywash', 'Junction', 'Mann Manor', 'Mountain Lab', 'Obscure', 'Steel', 'Well', 'Yukon'],
          'King of the Hill': ['Harvest', 'Harvest Event', 'Lakeside', 'Moonshine', 'Nucleus (King of the Hill)', 'Sawmill (King of the Hill)', 'Viaduct'],
          'Payload': ['Badwater Basin', 'Cashworks', 'Cranetop', 'Frontier', 'Gold Rush', 'Hoodoo', 'Swiftwater', 'Thunder Mountain', 'Upward', 'Waste'],
          'Payload Race': ['Animus', 'Cornfield', 'Hightower', 'Highwind', 'Nightfall', 'Panic', 'Pipeline', 'Scoville', 'Solitude'],
          'Territorial Control': ['Hydro', 'Meridian'],
          'Training mode': ['Dustbowl (Training)', 'Target', 'Walkway']
          }
     if 'article' not in kwargs:
         return t
     basetitle = u(kwargs['article'].title.split('/')[0])
     if t.getName().lower() != 'map infobox':
         return t
     for n in mapTypeDict:
          if basetitle in mapTypeDict[n]:
              t.setParam('game-type', n)
     return t
 addTemplateFilter(mapTypeFix)

Implement Backpack Item Link template

 def backpackLink(t, **kwargs):
     if t.getName().lower() != 'item infobox':
         return t
     if t.getParam('contributed-by') is None:
         return t
     contributor = t.getParam('contributed-by')
     optf2LinkReg = compileRegex(r'^\[http://optf2.com/item/(\S+)\s(.[^\[\]]+)]$')
     result = optf2LinkReg.search(contributor)
     if result:
         t.setParam('contributed-by', '{{Backpack Item Link|' + result.group(1) + '|' + result.group(2) + '}}')
     return t
 addTemplateFilter(backpackLink)

Implement {{Dictionary}}

 class DictionaryUpdater:
     def __init__(self):
         self.subpageTemplateLang = """{{#switch:{{{lang|{{SUBPAGENAME}}}}}|%options%}}<noinclude><hr style="margin: 1em 0em;" /><div style="font-size: 95%;">\n:[[File:Pictogram info.png|15px|text-top|link=]] '''Note''': Any changes made here will be automatically overwritten by a bot. Please ''do not'' make changes here as they will be lost. Edit '''[[:%dictionary%|the master page]]''' instead.\n:%missing%</div>[[Category:Template dictionary|%dictionaryname%/%keyname%]]</noinclude>"""
         self.subpageTemplateParam = """{{#switch:{{{1|}}}|%options%}}<noinclude><hr style="margin: 1em 0em;" /><div style="font-size: 95%;">\n:[[File:Pictogram info.png|15px|text-top|link=]] '''Note''': Any changes made here will be automatically overwritten by a bot. Please ''do not'' make changes here as they will be lost. Edit '''[[:%dictionary%|the master page]]''' instead.</div>[[Category:Template dictionary|%dictionaryname%/%keyname%]]</noinclude>"""
         self.invalidParamError = """<div style="font-size: 95%; color: #CC0000;">\n:[[File:Pictogram info.png|15px|text-top|link=]] '''Error''': Invalid parameter passed.</div>"""
         self.subpageTemplateID = """%string%<noinclude><hr style="margin: 1em 0em;" /><div style="font-size: 95%;">\n:[[File:Pictogram info.png|15px|text-top|link=]] '''Note''': Any changes made here will be automatically overwritten by a bot. Please ''do not'' make changes here as they will be lost. Edit '''[[:%dictionary%|the master page]]''' instead.</div>[[Category:Template dictionary|%dictionaryname%/%keyname%]]</noinclude>"""
         self.dictionaries = {
             u'Template:Dictionary/items': { # Dictionary page
                 'name': 'items', # Dictionary name (used for categorizing)
                 'sync': 'Template:Dictionary/items/Special:SyncData' # Page holding last sync data
             },
             u'Template:Dictionary/common strings': { # Warning: no underscore
                 'name': 'common strings',
                 'sync': 'Template:Dictionary/common strings/Special:SyncData'
             },
             u'Template:Dictionary/classes': {
                 'name': 'classes',
                 'sync': 'Template:Dictionary/classes/Special:SyncData'
             },
             u'Template:Dictionary/demonstrations': {
                 'name': 'demonstrations',
                 'sync': 'Template:Dictionary/demonstrations/Special:SyncData'
             },
             u'Template:Dictionary/price': {
                 'name': 'price',
                 'sync': 'Template:Dictionary/price/Special:SyncData'
             },
             u'Template:Dictionary/steam ids': {
                 'name': 'steam ids',
                 'sync': 'Template:Dictionary/steam ids/Special:SyncData'
             },
             u'Template:Dictionary/achievements/scout': {
                 'name': 'achievements/scout',
                 'sync': 'Template:Dictionary/achievements/scout/Special:SyncData'
             },
             u'Template:Dictionary/achievements/soldier': {
                 'name': 'achievements/soldier',
                 'sync': 'Template:Dictionary/achievements/soldier/Special:SyncData'
             },
             u'Template:Dictionary/achievements/pyro': {
                 'name': 'achievements/pyro',
                 'sync': 'Template:Dictionary/achievements/pyro/Special:SyncData'
             },
             u'Template:Dictionary/achievements/demoman': {
                 'name': 'achievements/demoman',
                 'sync': 'Template:Dictionary/achievements/demoman/Special:SyncData'
             },
             u'Template:Dictionary/achievements/heavy': {
                 'name': 'achievements/heavy',
                 'sync': 'Template:Dictionary/achievements/heavy/Special:SyncData'
             },
             u'Template:Dictionary/achievements/engineer': {
                 'name': 'achievements/engineer',
                 'sync': 'Template:Dictionary/achievements/engineer/Special:SyncData'
             },
             u'Template:Dictionary/achievements/medic': {
                 'name': 'achievements/medic',
                 'sync': 'Template:Dictionary/achievements/medic/Special:SyncData'
             },
             u'Template:Dictionary/achievements/sniper': {
                 'name': 'achievements/sniper',
                 'sync': 'Template:Dictionary/achievements/sniper/Special:SyncData'
             },
             u'Template:Dictionary/achievements/spy': {
                 'name': 'achievements/spy',
                 'sync': 'Template:Dictionary/achievements/spy/Special:SyncData'
             },
             u'Template:Dictionary/achievements/general': {
                 'name': 'achievements/general',
                 'sync': 'Template:Dictionary/achievements/general/Special:SyncData'
             },
             u'Template:Dictionary/achievements/halloween': {
                 'name': 'achievements/halloween',
                 'sync': 'Template:Dictionary/achievements/halloween/Special:SyncData'
             },
             u'Template:Dictionary/achievements/treasure hunt': {
                 'name': 'achievements/treasure hunt',
                 'sync': 'Template:Dictionary/achievements/treasure hunt/Special:SyncData'
             },
             u'Template:Dictionary/achievements/replay': {
                 'name': 'achievements/replay',
                 'sync': 'Template:Dictionary/achievements/replay/Special:SyncData'
             }
         }
         self.subpageSeparator = u'/'
         # List of supported languages, in prefered order
         self.languages = [u'en', u'ar', u'cs', u'da', u'de', u'es', u'fi', u'fr', u'hu', u'it', u'ja', u'ko', u'nl', u'no', u'pl', u'pt', u'pt-br', u'ro', u'ru', u'sv', u'tr', u'zh-hans', u'zh-hant']
         self.defaultLang = u'en'
         self.filterName = u'Your friendly neighborhood dictionary updater'
         self.commentsExtract = compileRegex(r)
         self.stringsExtract = compileRegex(r'(?:^[ \t]*#[ \t]*([^\r\n]*?)[ \t]*$\s*)?^[ \t]*([^\r\n]+?[ \t]*(?:\|[ \t]*[^\r\n]+?[ \t]*)*):[ \t]*([^\r\n]+?[ \t]*$|\s*[\r\n]+(?:\s*[ \t]+[-\w]+[ \t]*:[ \t]*[^\r\n]+[ \t]*$)+)', re.IGNORECASE | re.MULTILINE)
         self.translationExtract = compileRegex(r'^[ \t]+([-\w]+)[ \t]*:[ \t]*([^\r\n]+)[ \t]*$', re.IGNORECASE | re.MULTILINE)
         addWhitelistPage(self.dictionaries.keys())
     def generateSubpage(self, keyName, data, currentDict, syncData):
         h = hashlib.md5()
         if type(data) is type({}): # Subkeys (translations or not)
             isTranslation = True
             subpage = u(self.subpageTemplateLang)
             for k in data:
                 if k not in self.languages:
                     isTranslation = False
                     subpage = u(self.subpageTemplateParam)
                     break
             ordered = []
             if isTranslation:
                 missing = []
                 for lang in self.languages:
                     if lang in data:
                         ordered.append(lang + u'=' + data[lang])
                         h.update((lang + u'=' + data[lang]).encode('utf8'))
                     else:
                         missing.append(lang)
                         h.update((u'null-' + lang).encode('utf8'))
                 if self.defaultLang in data:
                     ordered.append(u'#default=' + data[self.defaultLang])
                 if len(missing):
                     subpage = subpage.replace(u'%missing%', u"'''Languages missing''': " + u', '.join(missing))
                 else:
                     subpage = subpage.replace(u'%missing%', u"'''Supported languages''': All")
             else: # Not a translation
                 h.update('Any-')
                 subkeys = data.keys()
                 subkeys.sort()
                 for k in subkeys:
                     ordered.append(k + u'=' + data[k])
                     h.update((k + u'=' + data[k]).encode('utf8'))
                 #ordered.append(u'#default=' + u(self.invalidParamError))
             subpage = subpage.replace(u'%options%', u'|'.join(ordered))
         else: # No subkeys
             data = u(data)
             subpage = self.subpageTemplateID
             h.update(u(u'ID-' + data).encode('utf8'))
             subpage = subpage.replace(u'%string%', data)
         h = u(h.hexdigest())
         if keyName in syncData and syncData[keyName] == h:
             return # Same hash
         syncData[keyName] = h # Update sync data
         subpage = subpage.replace(u'%dictionary%', currentDict)
         subpage = subpage.replace(u'%dictionaryname%', self.dictionaries[currentDict]['name'])
         subpage = subpage.replace(u'%keyname%', keyName)
         editPage(currentDict + self.subpageSeparator + keyName, subpage, summary=u'Pushed changes from [[:' + currentDict + u']] for string "' + keyName + u'".', minor=True, nocreate=False)
     def processComment(self, commentString, currentDict, definedStrings, syncData):
         commentContents = []
         for extractedStr in self.stringsExtract.finditer(commentString):
             comment = u
             if extractedStr.group(1):
                 comment = u'# ' + u(extractedStr.group(1)) + u'\n'
             dataString = u(extractedStr.group(3))
             if dataString.find(u'\r') == -1 and dataString.find(u'\n') == -1: # Assume no subkeys
                 data = dataString.strip()
                 dataWriteback = u' ' + data
             else: # There's subkeys; detect whether this is a translation or not
                 data = {}
                 isTranslation = True
                 for translation in self.translationExtract.finditer(dataString.rstrip()):
                     data[u(translation.group(1))] = u(translation.group(2))
                     if u(translation.group(1)) not in self.languages:
                         isTranslation = False
                 ordered = []
                 if isTranslation:
                     for lang in self.languages:
                         if lang in data:
                             ordered.append(u'  ' + lang + u': ' + data[lang])
                 else: # Not a translation, so order in alphabetical order
                     subkeys = data.keys()
                     subkeys.sort()
                     for subk in subkeys:
                         ordered.append(u'  ' + subk + u': ' + data[subk])
                 dataWriteback = u'\n' + u'\n'.join(ordered)
             keyNames = u(extractedStr.group(2)).lower().split(u'|')
             validKeyNames = []
             for keyName in keyNames:
                 keyName = keyName.replace(u'_', u' ').strip()
                 if keyName in definedStrings:
                     continue # Duplicate key
                 definedStrings.append(keyName)
                 validKeyNames.append(keyName)
                 self.generateSubpage(keyName, data, currentDict, syncData)
             if len(validKeyNames):
                 commentContents.append(comment + u' | '.join(validKeyNames) + u':' + dataWriteback)
         return u'\n\n'.join(commentContents)
     def __call__(self, content, **kwargs):
         if 'article' not in kwargs:
             return content
         if u(kwargs['article'].title) not in self.dictionaries:
             return content
         currentDict = u(kwargs['article'].title)
         syncPage = page(self.dictionaries[currentDict]['sync'])
         try:
             syncDataText = u(syncPage.getWikiText()).split(u'\n')
         except: # Page probably doesn't exist
             syncDataText = u
         syncData = {}
         for sync in syncDataText:
             sync = u(sync.strip())
             if not sync:
                 continue
             sync = sync.split(u':', 2)
             if len(sync) == 2:
                 syncData[sync[0]] = sync[1]
         oldSyncData = syncData.copy()
         newContent = u
         previousIndex = 0
         definedStrings = []
         for comment in self.commentsExtract.finditer(content):
             newContent += content[previousIndex:comment.start()]
             previousIndex = comment.end()
             # Process current comment
             newContent += u
         newContent += content[previousIndex:]
         # Check if we need to update sync data
         needUpdate = False
         for k in syncData:
             if k not in oldSyncData or oldSyncData[k] != syncData[k]:
                 needUpdate = True
                 break
         # Check for deleted strings
         for k in oldSyncData:
             if k not in definedStrings:
                 try:
                     deletePage(currentDict + self.subpageSeparator + k, 'Removed deleted string "' + k + u'" from ' + currentDict + u'.')
                 except:
                     pass
                 if k in syncData:
                     del syncData[k]
                 needUpdate = True
         if needUpdate:
             # Build syncdata string representation
             syncKeys = syncData.keys()
             syncKeys.sort()
             syncLines = []
             for k in syncKeys:
                 syncLines.append(k + u':' + syncData[k])
             editPage(syncPage, u'\n'.join(syncLines), summary=u'Updated synchronization information for [[:' + currentDict + u']].', minor=True, nocreate=False)
         return newContent
 addFilter(DictionaryUpdater())

Implement new Blueprint usage

 def blueprintUseFix(t, **kwargs):
     ClassTokenDict = {
          'Scout Token': 'Scout',
          'Soldier Token': 'Soldier',
          'Pyro Token': 'Pyro',
          'Demoman Token': 'Demoman',
          'Heavy Token': 'Heavy',
          'Engineer Token': 'Engineer',
          'Medic Token': 'Medic',
          'Sniper Token': 'Sniper',
          'Spy Token': 'Spy',
          'Class Token - Scout': 'Scout',
          'Class Token - Soldier': 'Soldier',
          'Class Token - Pyro': 'Pyro',
          'Class Token - Demoman': 'Demoman',
          'Class Token - Heavy': 'Heavy',
          'Class Token - Engineer': 'Engineer',
          'Class Token - Medic': 'Medic',
          'Class Token - Sniper': 'Sniper',
          'Class Token - Spy': 'Spy',
          }
     SlotTokenDict = {
          'Primary Token': 'primary',
          'Secondary Token': 'secondary',
          'Melee Token': 'melee',
          'PDA Token': 'pda2',
          'PDA2 Token': 'pda2',
          'Slot Token - Primary': 'primary',
          'Slot Token - Secondary': 'secondary',
          'Slot Token - Melee': 'melee',
          'Slot Token - PDA': 'pda2',
          'Slot Token - PDA2': 'pda2',
          }
     if 'article' not in kwargs:
         return t
     if t.getName().lower() != 'blueprint':
         return t
     if t.getParam('result-2') is not None:
         if t.getParam('ingredient-2') in ClassTokenDict and t.getParam('ingredient-3') in SlotTokenDict:
             autoresultstring = ClassTokenDict[t.getParam('ingredient-2')] + ' ' + SlotTokenDict[t.getParam('ingredient-3')]
         elif t.getParam('ingredient-3') in ClassTokenDict and t.getParam('ingredient-2') in SlotTokenDict:
             autoresultstring = ClassTokenDict[t.getParam('ingredient-3')] + ' ' + SlotTokenDict[t.getParam('ingredient-2')]
         elif t.getParam('ingredient-1') in ClassTokenDict and t.getParam('ingredient-2') in SlotTokenDict:
             autoresultstring = ClassTokenDict[t.getParam('ingredient-1')] + ' ' + SlotTokenDict[t.getParam('ingredient-2')]
         elif t.getParam('ingredient-1') in ClassTokenDict and t.getParam('ingredient-3') in SlotTokenDict:
             autoresultstring = ClassTokenDict[t.getParam('ingredient-1')] + ' ' + SlotTokenDict[t.getParam('ingredient-3')]
         elif t.getParam('ingredient-2') in ClassTokenDict and t.getParam('ingredient-1') in SlotTokenDict:
             autoresultstring = ClassTokenDict[t.getParam('ingredient-2')] + ' ' + SlotTokenDict[t.getParam('ingredient-1')]
         else:
             autoresultstring = ClassTokenDict[t.getParam('ingredient-3')] + ' ' + SlotTokenDict[t.getParam('ingredient-1')]
         t.delParam('ingredient-1')
         t.delParam('ingredient-2')
         t.delParam('ingredient-3')
         t.delParam('result')
         t.delParam('result-1')
         t.delParam('result-2')
         t.delParam('result-3')
         t.delParam('result-4')
         t.delParam('result-5')
         t.delParam('result-6')
         t.setParam('autoresult', autoresultstring)
     return t
 addTemplateFilter(blueprintUseFix)

File filters

PNGCrush/jpegtran all PNG/JPG images

 class imageCrushFilter:
     def __init__(self):
         self.minRatio = 10 # Compression ratio threshold
         self.minByteDiff = 2048 # Byte difference threshold
         self.jpgScanMap = u'0:   0  0 0 0 ;1 2: 0  0 0 0 ;0:   1  8 0 2 ;1:   1  8 0 0 ;2:   1  8 0 0 ;0:   9 63 0 2 ;0:   1 63 2 1 ;0:   1 63 1 0 ;1:   9 63 0 0 ;2:   9 63 0 0 ;'.replace(u';', u';\n')
         self.filterName = 'Saved crush information'
         self.extractHash = compileRegex(r'\{\{(?:png)?crush\s*\|\s*(\w+?)\s*\|\s*(\w+?)\s*}}')
         try:
             subprocess.call(['pngcrush', '-version'])
             self.pngenabled = True
         except:
             print 'Warning: PNGCrush is not installed or not in $PATH'
             self.pngenabled = False
         try:
             subprocess.call(['jpegtran', '-h'])
             self.jpgenabled = True
         except:
             print 'Warning: jpegtran is not installed or not in $PATH'
             self.jpgenabled = False
     def getRandBits(self):
         return random.getrandbits(128)
     def getFileHash(self, filename):
         h = hashlib.md5()
         f = open(filename, 'rb')
         for i in f.readlines():
             h.update(i)
         f.close()
         return u(h.hexdigest())
     def deleteFile(self, *fs):
         for f in fs:
             try:
                 os.remove(tempFile)
             except:
                 pass
     def __call__(self, content, article, **kwargs):
         title = u(article.title).lower()
         if title[-4:] == '.png':
             isPNG = True
             if not self.pngenabled:
                 return content
         elif title[-5:] == '.jpeg' or title[-4:] == '.jpg':
             isPNG = False
             if not self.jpgenabled:
                 return content
         else:
             return content
         try: # This is a high-risk filter, lots of I/O, so wrap it in a big try
             filePage = wikitools.wikifile.File(wiki(), article.title)
             hashes = [u, u]
             hashResult = self.extractHash.search(content)
             hashTemplate = None
             if hashResult:
                 hashes = [u(hashResult.group(1)), u(hashResult.group(2))]
                 hashTemplate = u'{{crush|' + hashes[0] + u'|' + hashes[1] + u'}}'
             tempFile = getTempFilename()
             filePage.download(location=tempFile, urlQuery=u(self.getRandBits()))
             oldHash = self.getFileHash(tempFile)
             if oldHash in hashes:
                 return content # Already worked on that one
             hashTemplate = u'{{crush|' + oldHash + u'|None}}'
             tempOutput = getTempFilename()
             if isPNG:
                 result = subprocess.call(['pngcrush', '-rem', 'gAMA', '-rem', 'cHRM', '-rem', 'iCCP', '-rem', 'sRGB', '-brute', tempFile, tempOutput])
             else:
                 mapFile = getTempFilename()
                 mapFileHandle = open(mapFile, 'wb')
                 mapFileHandle.write(self.jpgScanMap.encode('ascii')) # Onoz ASCII
                 mapFileHandle.close()
                 result = subprocess.call(['jpegtran', '-o', '-scans', mapFile, '-copy', 'none', '-progressive', '-outfile', tempOutput, tempFile])
                 self.deleteFile(mapFile)
             oldSize = os.path.getsize(tempFile)
             newSize = os.path.getsize(tempOutput)
             self.deleteFile(tempFile)
             if not result and oldSize > newSize:
                 # Ready to upload... or are we?
                 ratio = int(round(100 * (1.0 - float(newSize) / float(oldSize))))
                 if ratio >= self.minRatio or oldSize - newSize >= self.minByteDiff:
                     newHash = self.getFileHash(tempOutput)
                     if newHash in hashes:
                         self.deleteFile(tempOutput)
                         return content # Already got that result, no need to reupload
                     hashTemplate = u'{{crush|' + oldHash + u'|' + newHash + u'}}'
                     uploadFile(tempOutput, u(article.title), u'Crushed version: ' + u(ratio) + u'% reduction / ' + u(oldSize - newSize) + u' bytes saved; from ' + u(oldSize) + u' to ' + u(newSize) + u' bytes.', overwrite=True, reupload=True)
                     hashes = [oldHash, newHash]
             if hashResult:
                 content = content[:hashResult.start()] + hashTemplate + content[hashResult.end():]
             else:
                 content = content.strip() + u'\n\n' + hashTemplate
             self.deleteFile(tempOutput)
         except:
             pass # Well, that didn't work
         return content
 addFileFilter(imageCrushFilter())

Additional tasks

Update {{Blog link}}

 def updateTemplateBlogLink():
     fConfig = {
         'page': 'Template:Blog link',
         'begin': u,
         'end': u,
         'firstElement': u'[http://www.teamfortress.com/post.php?id={{#switch:{{{1|ERROR}}}',
         'lastElement': u'#default = ERROR}} {{{2|{{{1|blog post}}}}}}]',
         'rss': 'http://www.teamfortress.com/rss.xml',
         'linkRegex': compileRegex(r'id=(\d+)'),
         'timeoffset': -7
     }
     bl = page(fConfig['page'])
     content = u(bl.getWikiText())
     originalContent = content
     begin, end = content.find(fConfig['begin']), content.find(fConfig['end'])
     if begin == -1 or end == -1:
         return # Couldn't find begin/end
     try:
         feed = feedparser.parse(fConfig['rss'])
     except:
         return
     results = [fConfig['firstElement']]
     entries = 0
     for i in feed.entries:
         res = fConfig['linkRegex'].search(i.link)
         date = datetime.datetime.fromtimestamp(time.mktime(i.updated_parsed)) + datetime.timedelta(hours=fConfig['timeoffset'])
         if res:
             entries += 1
             day = date.strftime('%d')
             if day[0] == '0':
                 day = day[1:]
             results.append(u(date.strftime('%B ') + day + date.strftime(', %Y') + ' = ' + res.group(1)))
     results.append(fConfig['lastElement'])
     results = u'\n  | '.join(results)
     content = content[:begin+len(fConfig['begin'])] + results + content[end:]
     if content != originalContent and entries:
         editPage(bl, content, summary=u'Updated blog post list.')
 scheduleTask(updateTemplateBlogLink, 10)

Update List of Item Attributes

 def updateItemAttributes():
     try:
         attributes = urllib2.urlopen('http://optf2.com/attrib_dump?wikitext=1').read(-1)
     except:
         return
     if not attributes or attributes.find('<!DOCTYPE') != -1:
         return
     editPage('List of Item Attributes', '{{:List of Item Attributes/Header}}\n' + attributes + '\n{{:List of Item Attributes/Footer}}', summary=u'Updated item attributes', minor=True)
 scheduleTask(updateItemAttributes, 20)

Update {{Item checklist}} on list of subscribers

 def itemChecklists():
     def updateItemChecklist(checklist, schema, support):
         if not checklist.getParam('steamid'):
             checklist.setParam('error', 'Unspecified Steam ID.')
             return True
         supportedItems = {}
         for i in support:
             supportedItems[i] = 0
         try:
             steamUser = steam.user.profile(checklist.getParam('steamid'))
             backpack = steam.tf2.backpack(steamUser, schema=schema)
         except steam.user.ProfileError as e:
             checklist.setParam('error', u(e))
             return True
         except steam.tf2.TF2Error as e:
             if u(e) in (u'Bad SteamID64 given', u'Profile set to private'):
                 checklist.setParam('error', u(e))
                 return True
             return False
         for item in backpack:
             itemName = u(item.get_name()).lower()
             if itemName in supportedItems:
                 supportedItems[itemName] += 1
         for item in supportedItems:
             if supportedItems[item] > 1:
                 checklist.setParam(item, supportedItems[item])
             elif supportedItems[item] == 1:
                 checklist.setParam(item, 'yes')
             else:
                 p = checklist.getParam(item)
                 if p is not None:
                     p = p.lower()
                 if p in (None, 'no', '0'):
                     checklist.setParam(item, 'no')
                 elif p not in ('wanted', 'want', 'do not', 'anti', 'do not want'):
                     checklist.setParam(item, 'had')
         return True
     try:
         # *ahem* Because steamodd won't use singletons, requiring manual management of the schema object by the library user
         schema = steam.tf2.item_schema()
         allItems = []
         for item in schema:
             allItems.append(u(item.get_name()).lower())
     except:
         return # No schema means no fancy
     support = []
     templateParams = compileRegex(r'\{\{\{\s*([^{}|]+?)\s*\|')
     templateCode = page('Template:Item checklist').getWikiText()
     res = templateParams.search(templateCode)
     while res:
         item = u(res.group(1)).lower()
         if item not in support and item in allItems:
             support.append(item)
         templateCode = templateCode[res.end():]
         res = templateParams.search(templateCode)
     checkPage, checkLinks = linkExtract(page('User:WindBOT/Item_checklists').getWikiText())
     for i in range(3):
         checklist = page(random.choice(checkLinks).getLink())
         print 'Updating', checklist
         update = False
         oldContent = u(checklist.getWikiText())
         content, templatelist = templateExtract(oldContent)
         for t in templatelist:
             if t.getName().lower() == u'item checklist':
                 update = updateItemChecklist(t, schema, support)
                 break
         content = templateRestore(content, templatelist)
         if update and oldContent != content:
             editPage(checklist, content, summary=u'Updated Item checklist [[:' + u(checklist.title) + u']]', minor=True)
 scheduleTask(itemChecklists, 2)