Difference between revisions of "User:WindBOT/Filters"
Moussekateer (talk | contribs) (→Implement {{tl|Dictionary}}: +mann vs. machievements) |
m (→Moved links: testing) |
||
(316 intermediate revisions by 15 users not shown) | |||
Line 16: | Line 16: | ||
== Global filters == | == Global filters == | ||
− | === File categorization (Add {{tl|No category}}) === | + | === File categorization (Add {{tl|No category}} or {{tl|User image}}) === |
def fileCategorization(content, **kwargs): | 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'] | 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 = <nowiki>u'{{No category}}'</nowiki> | extraStuff = <nowiki>u'{{No category}}'</nowiki> | ||
+ | userImageCategory = <nowiki>u'[[Category:User images]]'</nowiki> | ||
+ | userAudioCategory = <nowiki>u'[[Category:User audio]]'</nowiki> | ||
+ | userTextCategory = <nowiki>u'[[Category:User text]]'</nowiki> | ||
regLang = compileRegex('/[^/]+$') | regLang = compileRegex('/[^/]+$') | ||
− | if 'article' not in kwargs or content.find(u'# | + | if 'article' not in kwargs or content.lower().find(u'#redirect') != -1: |
return content | return content | ||
title = u(kwargs['article'].title) | title = u(kwargs['article'].title) | ||
if title[:5].lower() != u'file:': | if title[:5].lower() != u'file:': | ||
return content | return content | ||
− | + | categories = kwargs['article'].getCategories() | |
+ | if title.lower().startswith(u'file:user '): | ||
+ | if title.lower().endswith(('.mp3', '.wav', '.ogg', '.flac')) and u'Category:User audio' not in categories and userAudioCategory not in content: | ||
+ | return (content.strip() + u'\n' + userAudioCategory).strip() | ||
+ | if title.lower().endswith(('.txt', '.cfg', '.diff', '.patch')) and u'Category:User text' not in categories and userTextCategory not in content: | ||
+ | return (content.strip() + u'\n' + userTextCategory).strip() | ||
+ | if title.lower().endswith(('.png', '.gif', '.jpg', '.jpeg', '.webp', '.apng', '.psd', '.webm')) and u'Category:User images' not in categories and userImageCategory not in content: | ||
+ | return (content.strip() + u'\n' + userImageCategory).strip() | ||
+ | return content # Don't go further | ||
+ | for c in categories: | ||
if u(regLang.sub(<nowiki>u''</nowiki>, c)) not in badLoneCategories: | if u(regLang.sub(<nowiki>u''</nowiki>, c)) not in badLoneCategories: | ||
return content | return content | ||
Line 37: | Line 49: | ||
== Page filters == | == Page filters == | ||
− | addPageFilter(r'^user:', r'(?:talk|help|wiki|template):') | + | addPageFilter(r'^user:', r'(?:talk|help|wiki|template|module):') |
== Semantic filters == | == Semantic filters == | ||
Line 55: | Line 67: | ||
'Unused content', | 'Unused content', | ||
'Promotional items', | 'Promotional items', | ||
− | 'Mercenary', 'Taunts', 'Decapitation', 'Fists', 'Cloak', # Too common to be reliably replaced | + | 'Mercenary', 'Taunts', 'Decapitation', 'Fists', 'Cloak', 'One-Man Army', # Too common to be reliably replaced |
'Club', 'Bat', 'Knife', 'Syringe', 'Bottle', 'Original', # Ditto (weapons) | 'Club', 'Bat', 'Knife', 'Syringe', 'Bottle', 'Original', # Ditto (weapons) | ||
'Classified', # Not capitalized even when referring to the hat | 'Classified', # Not capitalized even when referring to the hat | ||
'Attendant', # Avoids issue with French | 'Attendant', # Avoids issue with French | ||
− | 'Buff Banner', 'Medi Gun', 'Bonk Helm', # Avoids issue with German | + | 'Buff Banner', 'Medi Gun', 'Bonk Helm', 'B.A.S.E. Jumper', # Avoids issue with German |
+ | 'Voodoo Juju', # Conflicts Juju/JuJu | ||
'Reskins' # Not capitalized | 'Reskins' # Not capitalized | ||
] | ] | ||
Line 91: | Line 104: | ||
addSafeFilter(wordFilter('Spy', '(?<!I )Spy')) | addSafeFilter(wordFilter('Spy', '(?<!I )Spy')) | ||
classes.append('Spy') # Used in later functions with Spy included | classes.append('Spy') # Used in later functions with Spy included | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
=== Other capitalized words === | === Other capitalized words === | ||
enforceCapitalization('Team Fortress 2', 'TF2') | enforceCapitalization('Team Fortress 2', 'TF2') | ||
− | enforceCapitalization('Payload', ' | + | enforceCapitalization('Payload', 'Overtime', 'Dispensers', 'Sappers', 'Crits', 'Crit') |
enforceCapitalization('PlayStation', 'Xbox') | enforceCapitalization('PlayStation', 'Xbox') | ||
enforceCapitalization('iPod', 'iPhone') | enforceCapitalization('iPod', 'iPhone') | ||
Line 116: | Line 113: | ||
=== Word aliases === | === Word aliases === | ||
addSafeFilter( | addSafeFilter( | ||
− | wordFilter(u' | + | wordFilter(u'Ubersaw', u'[üÜ]bersaw'), # Ubersaw |
wordFilter(u'Force-a-Nature', u'Force of Nature'), # Force-A-Nature | wordFilter(u'Force-a-Nature', u'Force of Nature'), # Force-A-Nature | ||
wordFilter(u'ÜberCharges', u'[Üüu]ber(?!säge)(?!sage)(?! Entertainment)(?: ?charge)?s'), # ÜberCharges | wordFilter(u'ÜberCharges', u'[Üüu]ber(?!säge)(?!sage)(?! Entertainment)(?: ?charge)?s'), # ÜberCharges | ||
Line 155: | Line 152: | ||
wordFilter('Soldier', 'Solider'), | wordFilter('Soldier', 'Solider'), | ||
wordFilter('Mini-Crit', 'Minicrit'), | wordFilter('Mini-Crit', 'Minicrit'), | ||
+ | wordFilter('Flame Thrower', 'Flamethrower'), | ||
wordFilter('Mini-Crits', 'Minicrits'), | wordFilter('Mini-Crits', 'Minicrits'), | ||
+ | wordFilter('Mini-Crit', 'Mini-crit'), | ||
+ | wordFilter('Mini-Crits', 'Mini-crits'), | ||
+ | wordFilter('Critical hit', 'Critical Hit'), | ||
+ | wordFilter('Critical hits', 'Critical Hits'), | ||
wordFilter('Chargin\' Targe', 'charg[ei][-\'n\\s]*targe?'), | wordFilter('Chargin\' Targe', 'charg[ei][-\'n\\s]*targe?'), | ||
wordFilter('Kritzkrieg', 'Kritzkreig'), | wordFilter('Kritzkrieg', 'Kritzkreig'), | ||
Line 161: | Line 163: | ||
wordFilter('screenshots', 'screen shots'), | wordFilter('screenshots', 'screen shots'), | ||
wordFilter('in-game', 'ingame'), | wordFilter('in-game', 'ingame'), | ||
− | wordFilter('team-colored', 'team colou?red', keepcapitalization=True) | + | # wordFilter('team-colored', 'team colou?red', keepcapitalization=True) |
# wordFilter('color', '(?<!Rustic )colour') | # wordFilter('color', '(?<!Rustic )colour') | ||
) | ) | ||
addSafeFilter( | addSafeFilter( | ||
wordFilter('Natascha', 'Natas?c?ha'), | wordFilter('Natascha', 'Natas?c?ha'), | ||
+ | language='en' | ||
+ | ) | ||
+ | addSafeFilter( | ||
+ | wordFilter(u'tradable', u'tradeable', keepcapitalization=True), | ||
language='en' | language='en' | ||
) | ) | ||
Line 175: | Line 181: | ||
=== Map names === | === Map names === | ||
addSafeFilter( | addSafeFilter( | ||
− | wordFilter('Gravel Pit', '(?<!cp_)Gravelpit', 'Gravel pit'), | + | #wordFilter('Gravel Pit', '(?<!cp_)Gravelpit', 'Gravel pit'), - Conflicts with Gravelpit Emperor and its various translations |
wordFilter('Badwater Basin', '(?<!pl_)Badwater Basin', '(?<!pl_)Badwater(?!\s+Basin)'), | wordFilter('Badwater Basin', '(?<!pl_)Badwater Basin', '(?<!pl_)Badwater(?!\s+Basin)'), | ||
wordFilter('Gold Rush', '(?<!pl_)Goldrush') | wordFilter('Gold Rush', '(?<!pl_)Goldrush') | ||
Line 199: | Line 205: | ||
wordFilter(u'== See also ==', u'==+ ?See also ?==+'), | wordFilter(u'== See also ==', u'==+ ?See also ?==+'), | ||
wordFilter(u'== Related achievements ==', u'==+ ?Related achievements ?==+'), | wordFilter(u'== Related achievements ==', u'==+ ?Related achievements ?==+'), | ||
− | wordFilter(u'== Strange variant ==', u'==+ ?Strange variant ?==+') | + | wordFilter(u'== Strange variant ==', u'==+ ?Strange variant ?==+'), |
+ | wordFilter(u'=== Undocumented changes ===', u'==+ ?Undocumented changes ?==+') | ||
) | ) | ||
Line 243: | Line 250: | ||
=== German filters === | === German filters === | ||
addSafeFilter( # Requested by Picard | addSafeFilter( # Requested by Picard | ||
− | wordFilter(u'Krit-\'n-Cola', u'[CK]rit-\'?[an]\'?-Cola'), | + | wordFilter(u'Krit-a-Cola', u'(?<!Bonk! )[CK]rit-\'?[an]\'?-Cola'), |
+ | wordFilter(u'Bonk! Krit-\'n-Cola', u'Bonk! [CK]rit-\'?[an]\'?-Cola'), | ||
wordFilter(u'Krit', u'Crit'), | wordFilter(u'Krit', u'Crit'), | ||
wordFilter(u'Krits', u'Crits'), | wordFilter(u'Krits', u'Crits'), | ||
Line 272: | Line 280: | ||
wordFilter(u'Curiosidades', u'Trivia'), | wordFilter(u'Curiosidades', u'Trivia'), | ||
wordFilter(u'Actualizacion Mann-Conomy', u'Mann-Conomy Update'), | wordFilter(u'Actualizacion Mann-Conomy', u'Mann-Conomy Update'), | ||
+ | wordFilter(u'Übersaw', u'Ubersaw'), | ||
+ | wordFilter(u'Artículo', u'Objeto', keepcapitalization=True), | ||
+ | wordFilter(u'Artículos', u'Objetos', keepcapitalization=True), | ||
language='es' | language='es' | ||
) | ) | ||
Line 285: | Line 296: | ||
addSafeFilter(spanishDateFilter(), language='es') | addSafeFilter(spanishDateFilter(), language='es') | ||
− | addSafeFilter( # Requested by Dio | + | addSafeFilter( # Requested by Dio, and later updated as requested by Ashe |
− | wordFilter(u'== Variaciones de Colores ==', u'==+ *Variantes +pintadas *==+'), language='es' | + | wordFilter(u'== Variaciones de color ==', u'== Variaciones de Colores ==', u'==+ *Variantes +pintadas *==+'), language='es' |
) | ) | ||
Line 301: | Line 312: | ||
language='es' | language='es' | ||
) | ) | ||
+ | |||
+ | def spanishNivelCapitalizationFilter(t, **kwargs): # Capitalize "nivel" inside <nowiki>{{item infobox}}</nowiki> and <nowiki>{{backpack item}}</nowiki> templates, requested by rZ | ||
+ | if t.getName().lower() == 'item infobox' and t.getParam('level'): | ||
+ | t.setParam('level', t.getParam('level').replace('nivel', 'Nivel')) | ||
+ | if t.getName().lower() == 'backpack item' and t.getParam('item-level'): | ||
+ | t.setParam('item-level', t.getParam('item-level').replace('nivel', 'Nivel')) | ||
+ | return t | ||
+ | addTemplateFilter(spanishNivelCapitalizationFilter, language='es') | ||
=== French filters === | === French filters === | ||
Line 320: | Line 339: | ||
addTemplateFilter(portugueseInfoboxAvailabilityFilter, language='pt-br') | addTemplateFilter(portugueseInfoboxAvailabilityFilter, language='pt-br') | ||
− | addSafeFilter( # | + | addSafeFilter( # General |
wordFilter(u'Fabricação', u'Crafting', keepcapitalization=True), | wordFilter(u'Fabricação', u'Crafting', keepcapitalization=True), | ||
wordFilter(u'Conquista', u'Achievement', keepcapitalization=True), | wordFilter(u'Conquista', u'Achievement', keepcapitalization=True), | ||
wordFilter(u'Conquistas', u'Achievements', keepcapitalization=True), | wordFilter(u'Conquistas', u'Achievements', keepcapitalization=True), | ||
− | |||
− | |||
− | |||
− | |||
wordFilter(u'Demomen', u'Demomans'), | wordFilter(u'Demomen', u'Demomans'), | ||
wordFilter(u'Heavies', u'Heavys'), | wordFilter(u'Heavies', u'Heavys'), | ||
Line 334: | Line 349: | ||
wordFilter(u'Atualizações', u'Patches', keepcapitalization=True), | wordFilter(u'Atualizações', u'Patches', keepcapitalization=True), | ||
wordFilter(u'Desempenho', u'Performance', keepcapitalization=True), | wordFilter(u'Desempenho', u'Performance', keepcapitalization=True), | ||
− | |||
wordFilter(u'Spies', u'Spys'), | wordFilter(u'Spies', u'Spys'), | ||
− | wordFilter(u'ÜberCarga', u'Sobrecarga', u'Übercharge'), | + | wordFilter(u'ÜberCarga', u'Sobrecarga', u'Übercharge', u'ÜberCharge'), |
− | wordFilter(u'Mochila', u'Backpack', keepcapitalization=True), | + | wordFilter(u'Mochila', u'Backpack', u'Inventário', keepcapitalization=True), |
wordFilter(u'Comunidade Steam', u'Steam Community'), | wordFilter(u'Comunidade Steam', u'Steam Community'), | ||
− | |||
wordFilter(u'== Bugs ==', u'==+ *Defeitos *==+'), | wordFilter(u'== Bugs ==', u'==+ *Defeitos *==+'), | ||
wordFilter(u'Sala de renascimento', u'Respawn room', u'Sala de respawn', keepcapitalization=True), | wordFilter(u'Sala de renascimento', u'Respawn room', u'Sala de respawn', keepcapitalization=True), | ||
Line 346: | Line 359: | ||
wordFilter(u'Sistema de obtenção de itens', u'Sistema de drop de itens', u'Item drop system', u'Sistema de queda de itens', keepcapitalization=True), | wordFilter(u'Sistema de obtenção de itens', u'Sistema de drop de itens', u'Item drop system', u'Sistema de queda de itens', keepcapitalization=True), | ||
wordFilter(u'Rajada de ar', u'Compression blast', u'Airblast', keepcapitalization=True), | wordFilter(u'Rajada de ar', u'Compression blast', u'Airblast', keepcapitalization=True), | ||
− | wordFilter(u'minicrits', u'Mini-Crits', u'Mini-Críticos', u'Minicríticos'), | + | wordFilter(u'minicrits', u'Mini-Crits', u'Mini-Críticos', u'Minicríticos', keepcapitalization=True), |
+ | wordFilter(u'minicrit', u'Mini-Crit', u'Mini-Crítico', u'Minicrítico', keepcapitalization=True), | ||
wordFilter(u'Blog Oficial do TF2', u'TF2 Official Blog', u'Blog Oficial de TF2'), | wordFilter(u'Blog Oficial do TF2', u'TF2 Official Blog', u'Blog Oficial de TF2'), | ||
wordFilter(u'Personalizada', u'Customizada', keepcapitalization=True), | wordFilter(u'Personalizada', u'Customizada', keepcapitalization=True), | ||
wordFilter(u'Personalizado', u'Customizado', keepcapitalization=True), | wordFilter(u'Personalizado', u'Customizado', keepcapitalization=True), | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
wordFilter(u'Compartimento', u'Slot', keepcapitalization=True), | wordFilter(u'Compartimento', u'Slot', keepcapitalization=True), | ||
wordFilter(u'Jogabilidade', u'Gameplay', keepcapitalization=True), | wordFilter(u'Jogabilidade', u'Gameplay', keepcapitalization=True), | ||
wordFilter(u'Rei do Pedaço', u'King of the Hill'), | wordFilter(u'Rei do Pedaço', u'King of the Hill'), | ||
− | wordFilter(u'Disparo | + | wordFilter(u'Disparo-alt', u'Disparo-Alt', u'Alt-Fire', keepcapitalization=True), |
− | wordFilter(u'Botão de | + | wordFilter(u'Disparo alternativo', u'Fogo Alternativo', u'Disparo Alternativo', keepcapitalization=True), |
− | + | wordFilter(u'Botão de disparo-alt', u'Botão Direito do Mouse', keepcapitalization=True), | |
wordFilter(u'Habilidades', u'Abilidades', keepcapitalization=True), | wordFilter(u'Habilidades', u'Abilidades', keepcapitalization=True), | ||
wordFilter(u'Austrálio', u'Australium', keepcapitalization=True), | wordFilter(u'Austrálio', u'Australium', keepcapitalization=True), | ||
Line 368: | Line 376: | ||
wordFilter(u'Natal Australiano', u'Australian Christmas', keepcapitalization=True), | wordFilter(u'Natal Australiano', u'Australian Christmas', keepcapitalization=True), | ||
wordFilter(u'Natal Australiano de 2011', u'Natal Australiano 2011', u'Australian Christmas 2011', keepcapitalization=True), | wordFilter(u'Natal Australiano de 2011', u'Natal Australiano 2011', u'Australian Christmas 2011', keepcapitalization=True), | ||
− | wordFilter(u'== Variantes pintadas ==', u'==+ *Variantes pintados *==+', u'==+ *Variações pintadas *==+'), | + | wordFilter(u'== Variantes pintadas ==', u'==+ *Variantes pintados *==+', u'==+ *Variações pintadas *==+', u'==+ *Variantes pintáveis *==+'), |
wordFilter(u'== Curiosidades ==', u'==+ *Trivias? *==+'), | wordFilter(u'== Curiosidades ==', u'==+ *Trivias? *==+'), | ||
wordFilter(u'Projeto', u'Planta', keepcapitalization=True), | wordFilter(u'Projeto', u'Planta', keepcapitalization=True), | ||
wordFilter(u'Projetos', u'Plantas', keepcapitalization=True), | wordFilter(u'Projetos', u'Plantas', keepcapitalization=True), | ||
+ | wordFilter(u'== Histórico de atualizações ==', u'==+ *Histórico de atualização? *==+', u'==+ *Update history? *==+'), | ||
+ | wordFilter(u'pré-venda', u'pré-compra'), | ||
+ | wordFilter(u'Dia das Bruxas', u'Halloween', keepcapitalization=True), | ||
+ | wordFilter(u'Engine Source', u'Source Engine', u'Motor Source'), | ||
+ | wordFilter(u'itens', u'items', keepcapitalization=True), | ||
+ | wordFilter(u'pré-visualização', u'preview', u'previsão', keepcapitalization=True), | ||
+ | wordFilter(u'pré-visualizações', u'previews', u'previsões', keepcapitalization=True), | ||
+ | wordFilter(u'no Steam', u'na Steam+'), | ||
+ | wordFilter(u'do Steam', u'da Steam+'), | ||
+ | wordFilter(u'à Oficina Steam', u'para a Oficina Steam+'), | ||
+ | wordFilter(u'cor da equipe', u'cor do time', u'cor de time', u'cor de equipe'), | ||
+ | wordFilter(u'Minissentinelas', u'Minisentinelas', u'Mini-Sentinelas, keepcapitalization=True'), | ||
+ | wordFilter(u'Minissentinela', u'Minisentinela', u'Mini-Sentinela, keepcapitalization=True'), | ||
+ | wordFilter(u'Natal', u'Smissmas'), | ||
+ | wordFilter(u'MvM', u'MVM'), | ||
+ | wordFilter(u'Mann vs. Máquina', u'Mann vs Máquina', u'Mann vs. Machine', u'Mann vs Machine'), | ||
+ | wordFilter(u'interface', u'HUD', keepcapitalization=True), | ||
+ | wordFilter(u'captura de tela', u'screenshot', keepcapitalization=True), | ||
+ | wordFilter(u'capturas de tela', u'screenshots', keepcapitalization=True), | ||
+ | wordFilter(u'→', u'->', u'-->'), | ||
+ | wordFilter(u'←', u'<-', u'<--'), | ||
+ | wordFilter(u'conversa', u'bate-papo', u'bate papo', u'chat', keepcapitalization=True), | ||
+ | wordFilter(u'on-line', u'online', keepcapitalization=True), | ||
+ | wordFilter(u'off-line', u'offline', keepcapitalization=True), | ||
+ | wordFilter(u'tiros fatais na cabeça', u'tiros na cabeça fatais', keepcapitalization=True), | ||
+ | wordFilter(u'Loja Mann Co\.', u'Loja Mann Co\.\.', u'Loja da Mann Co\.'), | ||
+ | wordFilter(u'Peça Estranha', u'parte estranha'), | ||
language='pt-br' | language='pt-br' | ||
) | ) | ||
− | + | addSafeFilter( # Quality names | |
− | + | wordFilter(u'Genuíno', u'Genuine', keepcapitalization=True), | |
+ | wordFilter(u'Feito por Mim', u'Self-Made', keepcapitalization=True), | ||
+ | wordFilter(u'Normal', u'Stock', keepcapitalization=True), | ||
+ | wordFilter(u'Estranho', u'Strange', keepcapitalization=True), | ||
+ | wordFilter(u'Único', u'Unique', keepcapitalization=True), | ||
+ | wordFilter(u'Incomum', u'Unusual', keepcapitalization=True), | ||
+ | language='pt-br' | ||
) | ) | ||
− | addSafeFilter( # | + | addSafeFilter( # Weapon names |
wordFilter(u'Espingarda', u'Scattergun'), | wordFilter(u'Espingarda', u'Scattergun'), | ||
wordFilter(u'Pistola', u'Pistol'), | wordFilter(u'Pistola', u'Pistol'), | ||
Line 386: | Line 427: | ||
wordFilter(u'Escopeta', u'Shotgun'), | wordFilter(u'Escopeta', u'Shotgun'), | ||
wordFilter(u'Pá', u'Shovel'), | wordFilter(u'Pá', u'Shovel'), | ||
− | wordFilter(u'Lança-Chamas', u'Flamethrower'), | + | wordFilter(u'Lança-Chamas', u'Flame Thrower' u'Flamethrower'), |
wordFilter(u'Machado de Incêndio', u'Fire Axe'), | wordFilter(u'Machado de Incêndio', u'Fire Axe'), | ||
wordFilter(u'Lança-Granadas', u'Grenade Launcher'), | wordFilter(u'Lança-Granadas', u'Grenade Launcher'), | ||
Line 398: | Line 439: | ||
wordFilter(u'Arma Médica', u'Medi Gun'), | wordFilter(u'Arma Médica', u'Medi Gun'), | ||
wordFilter(u'Serra de Ossos', u'Bone Saw'), | wordFilter(u'Serra de Ossos', u'Bone Saw'), | ||
− | wordFilter(u'Rifle de Sniper', u'Sniper Rifle'), | + | wordFilter(u'Rifle de Precisão', u'Rifle de Sniper', u'Sniper Rifle', keepcapitalization=True), |
wordFilter(u'Submetralhadora', u'SMG'), | wordFilter(u'Submetralhadora', u'SMG'), | ||
wordFilter(u'Revólver', u'Revolver'), | wordFilter(u'Revólver', u'Revolver'), | ||
Line 404: | Line 445: | ||
wordFilter(u'Relógio de Invisibilidade', u'Invis Watch', u'Invisibility Watch'), | wordFilter(u'Relógio de Invisibilidade', u'Invis Watch', u'Invisibility Watch'), | ||
wordFilter(u'Kit de Disfarce', u'Disguise Kit'), | wordFilter(u'Kit de Disfarce', u'Disguise Kit'), | ||
− | + | language='pt-br' | |
+ | ) | ||
+ | |||
+ | enforceCapitalization( # Do not capitalize languages, nationalities, months or days of the week (will not include abbrevs. of weekdays, as they are also ordinal numbers) | ||
+ | u'inglês', u'russo', u'russa', u'francês', u'francesa', u'alemão', u'alemã', u'polonês', u'polonesa', u'brasileiro', u'brasileira', u'finlandês', u'finlandesa', u'castelhano', u'espanhol', u'espanhola', u'holandês', u'holandesa', u'chinês', u'chinesa', u'chinês simplificado', u'chinês tradicional', u'tailandês', u'tailandesa', u'ucraniano', u'ucraniana', u'árabe', u'tcheco', u'tcheca', u'dinamarquês', u'dinamarquesa', u'húngaro', u'húngara', u'italiano', u'italiana', u'japonês', u'japonesa', u'coreano', u'coreana', u'norueguês', u'norueguesa', u'português', u'portuguesa', u'romeno', u'romena', u'sueco', u'sueca', u'turco', u'egípcio', u'egípcia', u'janeiro', u'fevereiro', u'março', u'abril', u'maio', u'junho', u'julho', u'agosto', u'setembro', u'outubro', u'novembro', u'dezembro', u'segunda-feira', u'terça-feira', u'quarta-feira', u'quinta-feira', u'sexta-feira', u'sábado', u'domingo', language='pt-br' | ||
) | ) | ||
Line 441: | Line 486: | ||
wordFilter(u'Galerij', u'Gallerij', keepcapitalization=True), | wordFilter(u'Galerij', u'Gallerij', keepcapitalization=True), | ||
wordFilter(u'community', u'communitie', keepcapitalization=True), | wordFilter(u'community', u'communitie', keepcapitalization=True), | ||
+ | language='nl' | ||
+ | ) | ||
+ | |||
+ | addSafeFilter( # Requested by Robin0van0der0vliet | ||
+ | wordFilter(u'officieel', u'offici[eë][eë]l', keepcapitalization=True), | ||
+ | wordFilter(u'officiële', u'offic[iï]ele', keepcapitalization=True), | ||
+ | language='nl' | ||
+ | ) | ||
+ | |||
+ | addSafeFilter( # Requested by Eels | ||
+ | wordFilter(u'voorwerpvindsysteem', u'voorwerp vind systeem', keepcapitalization=True), | ||
+ | wordFilter(u'Voorwerpschema', u'Voorwerp schema', keepcapitalization=True), | ||
+ | wordFilter(u'Voorwerpschemaupdate', u'Voorwerp schema-update', u'Voorwerpschema update', keepcapitalization=True), | ||
+ | language='nl' | ||
+ | ) | ||
+ | |||
+ | addSafeFilter( # Requested by GrampaSwood | ||
+ | regex(r'\b, en\b', ' en'), | ||
+ | wordFilter(u'grondvoorwerpen', u'pickups', keepcapitalization=True), | ||
+ | wordFilter(u'grondvoorwerp', u'pickup', keepcapitalization=True), | ||
+ | wordFilter(u'bijgewerkt', u'geüpdatet', keepcapitalization=True), | ||
+ | wordFilter(u'weetjes', u'trivia', keepcapitalization=True), | ||
language='nl' | language='nl' | ||
) | ) | ||
Line 451: | Line 518: | ||
wordFilter(u'Exploitu', u'Nadużycia', keepcapitalization=True), | wordFilter(u'Exploitu', u'Nadużycia', keepcapitalization=True), | ||
language='pl' | language='pl' | ||
+ | ) | ||
+ | |||
+ | === Hungarian filters === | ||
+ | addSafeFilter( # Requested by Monte | ||
+ | wordFilter(u'== Frissítési előzmények ==', u'==+ ?*Frissítések ?==+', u'==+ ?*Update ?==+', u'==+ ?*Javítások ?==+'), | ||
+ | wordFilter(u'== Lásd még ==', u'==+ ?See also ?==+'), | ||
+ | wordFilter(u'== Források ==', u'==+ ?References ?==+'), | ||
+ | wordFilter(u'== Kulisszák mögött ==', u'==+ ?Érdekességek ?==+', u'==+ ? Trivia ?==+'), | ||
+ | wordFilter(u'== Festett variációk ==', u'==+ ?Painted variants ?==+'), | ||
+ | wordFilter(u'== Sebzési és működési idők ==', u'==+ ?Damage and function times ?==+', u'==+ ?Sebzési? és [Ff]unkció idők ?==+'), | ||
+ | wordFilter(u'=== Mint barkácsolási kellék ===', u'==+ ?As a crafting ingredient ?==+'), | ||
+ | wordFilter(u'== Nem használt tartalom ==', u'==+ ?Unused content ?==+'), | ||
+ | wordFilter(u'== Öszefüggő teljesítmények ==', u'==+ ?Related achievements ?==+', u'==+ ?[Aa]chievements ?==+', u'==+ ?Teljesítmények ?==+'), | ||
+ | wordFilter(u'== Fura ritkaságú ==', u'==+ ?Strange variant ?==+'), | ||
+ | wordFilter(u'== Tárgy-szett ==', u'==+ ?Item set ?==+', u'==+ ?Tárgyszett ?==+', u'==+ ?Tárgy szett ?==+'), | ||
+ | language='hu' | ||
+ | ) | ||
+ | |||
+ | === Swedish filters === | ||
+ | addSafeFilter( # Requested by BrazilianNut | ||
+ | wordFilter(u'Ingenjörer', u'Tekniker', keepcapitalization=True), | ||
+ | wordFilter(u'Ingenjören', u'Teknikern', keepcapitalization=True), | ||
+ | language='sv' | ||
) | ) | ||
== Link filters == | == Link filters == | ||
+ | === tf2.com to teamfortress.com === | ||
+ | addLinkFilter(linkDomainFilter('tf2.com', 'teamfortress.com')) | ||
+ | |||
=== Moved links === | === Moved links === | ||
def movedLinks(link, **kwargs): | def movedLinks(link, **kwargs): | ||
Line 465: | Line 558: | ||
u'Quality#Community': u'Community (quality)', | u'Quality#Community': u'Community (quality)', | ||
u'Quality#Self-Made': u'Self-Made', | u'Quality#Self-Made': u'Self-Made', | ||
− | u'Quality#Valve': u'Valve (quality)' | + | u'Quality#Valve': u'Valve (quality)', |
+ | u'vdc:Material': u'vdc:VMT' | ||
} | } | ||
if link.getType() == u'internal' and link.getLink() in movedLinks: | if link.getType() == u'internal' and link.getLink() in movedLinks: | ||
Line 492: | Line 586: | ||
addLinkFilter(wikipediaLinks) | addLinkFilter(wikipediaLinks) | ||
− | === | + | === Fix external wiki links that should be internal links === |
− | def | + | def fixExternalToInternalLinks(link, **kwargs): |
− | + | wikiExternalRe = compileRegex(r'^https?://wiki\.(teamfortress|tf2)\.com/wiki/(\S+)$') | |
− | |||
if link.getType() == 'external': | if link.getType() == 'external': | ||
− | + | linkMatch = wikiExternalRe.search(link.getLink()) | |
− | + | if linkMatch: | |
− | |||
− | |||
− | |||
− | if | ||
link.setType('internal') | link.setType('internal') | ||
try: | try: | ||
− | wikiPage = u(urllib2.unquote(str( | + | wikiPage = u(urllib2.unquote(str(linkMatch.group(2))).decode('utf8', 'ignore').replace(u'_', ' ')) |
except: | except: | ||
− | wikiPage = u( | + | wikiPage = u(linkMatch.group(2)).replace(u'_', ' ') |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
link.setLink(wikiPage) | link.setLink(wikiPage) | ||
if link.getLabel() is None: | if link.getLabel() is None: | ||
− | link.setLabel( | + | link.setLabel(wikiPage) |
return link | return link | ||
− | addLinkFilter( | + | addLinkFilter(fixExternalToInternalLinks) |
=== Category removal on pages using {{tl|Item infobox}} === | === Category removal on pages using {{tl|Item infobox}} === | ||
Line 530: | Line 613: | ||
return None | return None | ||
addLinkFilter(removeCategory) | addLinkFilter(removeCategory) | ||
− | |||
− | |||
− | |||
=== Remove trailing slashes from internal links === | === Remove trailing slashes from internal links === | ||
Line 578: | Line 658: | ||
'Sniper Nav': ['sniper nav/ro'], | 'Sniper Nav': ['sniper nav/ro'], | ||
'Spy Nav': ['spy nav/ro'], | 'Spy Nav': ['spy nav/ro'], | ||
− | 'Promo | + | 'Promo Nav': ['mncnav', 'pokernightnav', 'l4dnav', 'kfnav'], |
− | 'Crush': ['pngcrush'] | + | 'Crush': ['pngcrush'], |
+ | 'Class infobox': ['infobox class'], | ||
+ | 'Video infobox': ['infobox video'], | ||
+ | 'Comic infobox': ['infobox comics'] | ||
} | } | ||
for n in templateMap: | for n in templateMap: | ||
Line 586: | Line 669: | ||
return t | return t | ||
addTemplateFilter(templateRenameMapping) | addTemplateFilter(templateRenameMapping) | ||
+ | |||
+ | === Lang template renaming === | ||
+ | def langsTemplateRenameMapping(t, **kwargs): | ||
+ | templateMap = {'Scout Nav': 'scout nav', | ||
+ | 'Soldier Nav': 'soldier nav', | ||
+ | 'Pyro Nav': 'pyro nav', | ||
+ | 'Demoman Nav': 'demoman nav', | ||
+ | 'Heavy Nav': 'heavy nav', | ||
+ | 'Engineer Nav': 'engineer nav', | ||
+ | 'Medic Nav': 'medic nav', | ||
+ | 'Sniper Nav': 'sniper nav', | ||
+ | 'Spy Nav': 'spy nav', | ||
+ | 'Audio Nav': 'audionav', | ||
+ | 'Video Nav': 'videonav', | ||
+ | 'Class Strategy Nav': 'class strategy' | ||
+ | } | ||
+ | for template in templateMap: | ||
+ | langTemplateRe = compileRegex(templateMap[template] + '/(ar|cs|da|de|es|fi|fr|hu|it|ja|ko|nl|no|pl|pt|pt-br|ro|ru|sv|tr|zh-hans|zh-hant)') | ||
+ | if langTemplateRe.match(t.getName().lower()): | ||
+ | t.setName(template) | ||
+ | return t | ||
+ | addTemplateFilter(langsTemplateRenameMapping) | ||
+ | |||
+ | === Reindent all infoboxes === | ||
+ | |||
+ | def infoboxIndentFilter(t, **kwargs): | ||
+ | itemInfoboxes = ('item infobox', 'map infobox', 'item set infobox', 'mission infobox', 'class infobox', 'hazard infobox', 'pickup infobox', 'video infobox', 'comic infobox', 'website infobox', 'company infobox') | ||
+ | tName = t.getName().lower() | ||
+ | if 'infobox' in tName and tName not in itemInfoboxes: | ||
+ | t.indentationMatters(True) | ||
+ | t.setDefaultIndentation(2) | ||
+ | return t | ||
+ | addTemplateFilter(infoboxIndentFilter, lowPriority=True) | ||
=== Manage all {{tl|Item infobox}}es === | === Manage all {{tl|Item infobox}}es === | ||
Line 616: | Line 732: | ||
catsDontCheck = [u'Category:Beta and unused content', 'Category:Taunts'] # Type and slot won't be modified on these pages | 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 | preferedOrder = [ # Prefered order of keys inside template | ||
− | 'name', 'game', 'type', 'beta', 'unused', 'image', 'imagewidth', '3d-team', '3d-alt', '3d-team-alt', 'number-of-3d-images', 'number-of-3d-team-images', 'number-of-3d-alt-images', 'view#', 'view#name', 'team-colors', 'team-colors-class#', 'two-models', 'skin-image-red', 'skin-image-blu', ' | + | 'name', 'game', 'type', 'beta', 'unused', 'image', 'imagewidth', '3d-team', '3d-alt', '3d-team-alt', '3d-image-#', '3d-button-#', '3d-viewname-#', 'number-of-3d-images', 'number-of-3d-team-images', 'number-of-3d-alt-images', 'view#', 'view#name', 'team-colors', 'team-colors-class#', 'team-colors-class#-name', 'team-colors-width', 'two-models', 'skin-image-red', 'skin-image-blu', 'tfc-model', 'tfc-model-3d-image-#', 'tfc-model-3d-viewname-#', 'tfc-model-3d-button-#', 'qtf-model', 'hide-kill-icon', 'kill-icon-#', 'kill-text-#', 'kill-tooltip-#', 'used-by', 'slot', 'crafting-slot', 'custom-slot', 'equip-region', 'equip-region-#', 'weapon-script', 'contributed-by', 'released', 'released-major', 'availability', 'trade', 'gift', 'marketable', 'craft', 'paint', 'rename', 'numbered', 'medieval', 'ammo-loaded', 'ammo-carried', 'ammo-type', 'show-ammo', 'reload', 'loadout', 'loadout-prefix', 'prefix', 'suffix', 'quality', '%ATTRIBUTES%', 'item-kind', 'item-level', 'level', 'paint-color', 'decal-icon', 'unusual-icon', 'stat-icon', 'strange-icon', 'pyroland-icon', 'halloween-icon', 'limited', 'unusual-effect', 'grade', 'wear', 'item-description', 'item-uses', 'item-flags', 'item-expiration' |
] | ] | ||
− | checkEnglish = ['trade', 'gift', 'craft', 'paint', 'rename', 'numbered', 'medieval'] # If these attributes aren't yes or no, check the values on the english page | + | checkEnglish = ['trade', 'gift', 'marketable', 'craft', 'paint', 'rename', 'numbered', 'medieval', 'contributed-by', 'equip-region', 'equip-region-#'] # If these attributes aren't yes or no, check the values on the english page |
attributeTypes = ['neutral', 'positive', 'negative'] # Possible loadout attribute types | attributeTypes = ['neutral', 'positive', 'negative'] # Possible loadout attribute types | ||
regLang = compileRegex('/[^/]+$') | regLang = compileRegex('/[^/]+$') | ||
Line 629: | Line 745: | ||
tCats = [] | tCats = [] | ||
for c in cats2: | for c in cats2: | ||
− | tCats.append(u(regLang.sub(u'', u(c)))) | + | tCats.append(u(regLang.sub(<nowiki>u''</nowiki>, u(c)))) |
isTFC = u'Category:Weapons (Classic)' in tCats | isTFC = u'Category:Weapons (Classic)' in tCats | ||
# Step 1 - Rename obsolete attributes | # Step 1 - Rename obsolete attributes | ||
Line 663: | Line 779: | ||
values[attr] = t.getParam(attr).lower() | values[attr] = t.getParam(attr).lower() | ||
if regLang.search(kwargs['article'].title): | if regLang.search(kwargs['article'].title): | ||
− | englishArticle = page(regLang.sub('', kwargs['article'].title)) | + | englishArticle = page(regLang.sub(<nowiki>u''</nowiki>, kwargs['article'].title)) |
try: | try: | ||
englishContent = englishArticle.getWikiText() | englishContent = englishArticle.getWikiText() | ||
Line 678: | Line 794: | ||
englishT.renameParam(paramAliases[p], p) | englishT.renameParam(paramAliases[p], p) | ||
for attr in checkEnglish: | for attr in checkEnglish: | ||
− | if englishT.getParam(attr) is not None | + | if englishT.getParam(attr) is not None: |
− | values[attr] = englishT.getParam(attr) | + | values[attr] = templateRestore(englishT.getParam(attr), englishTemplates, englishKeys) |
break | break | ||
for attr in values: | for attr in values: | ||
Line 696: | Line 812: | ||
for c in catstoCheck[cname]: | for c in catstoCheck[cname]: | ||
cat, val = u(c).split(u'|') | cat, val = u(c).split(u'|') | ||
− | cat = u'Category:' + u(regLang.sub(u'', cat)) | + | cat = u'Category:' + u(regLang.sub(<nowiki>u''</nowiki>, cat)) |
if cat in tCats: | if cat in tCats: | ||
if found is not None: | if found is not None: | ||
Line 727: | Line 843: | ||
t.setParam('game', 'tfc') | t.setParam('game', 'tfc') | ||
# Step 9 - Set correct preferred indentation | # Step 9 - Set correct preferred indentation | ||
− | for k in ['quality', 'level', 'item-description', 'item-uses', 'item-flags', 'level-and-type', 'loadout-name', 'loadout-prefix', 'hide-loadout-prefix']: | + | for k in ['quality', 'item-level', 'level', 'item-description', 'item-uses', 'item-flags', 'level-and-type', 'loadout-name', 'loadout-prefix', 'hide-loadout-prefix', 'limited', 'grade', 'wear', 'unusual-effect', 'item-kind', 'prefix', 'suffix', 'decal-icon', 'unusual-icon', 'stat-icon', 'strange-icon', 'pyroland-icon', 'halloween-icon', 'custom-icon', 'killcount', 'rankson', 'rankson2', 'rankson3', 'rankson4', 'rankson5', 'rankson6', 'rankson7', 'rankson8', 'rankson9', 'item-expiration']: |
t.setPreferedIndentation(k, 2) | t.setPreferedIndentation(k, 2) | ||
for i in range(1, 10): | for i in range(1, 10): | ||
Line 742: | Line 858: | ||
for a in attributeTypes: | for a in attributeTypes: | ||
newOrder.append('att-' + str(i) + '-' + a) | newOrder.append('att-' + str(i) + '-' + a) | ||
− | for i in range(1, | + | for i in range(1, 30): |
newOrder.append(o.replace('#', str(i))) | newOrder.append(o.replace('#', str(i))) | ||
t.setPreferedOrder(newOrder) | t.setPreferedOrder(newOrder) | ||
− | # Step 11 - There is no step | + | # Step 11 - replace deprecated hat/misc types |
+ | if t.getParam('type') is not None and t.getParam('type').lower() in (u'hat', u'hats', u'head', u'headwear', u'misc item', u'misc.', u'misc', u'miscellaneous', u'miscellaneous item'): | ||
+ | t.setParam('type', 'cosmetic') | ||
+ | # Step 12 - There is no step 12 | ||
return t | return t | ||
addTemplateFilter(infoboxFilter) | addTemplateFilter(infoboxFilter) | ||
Line 755: | Line 874: | ||
return t | return t | ||
addTemplateFilter(removeUselessTemplate) | addTemplateFilter(removeUselessTemplate) | ||
+ | |||
+ | === Replace Wikipedia link template with interwiki link === | ||
+ | def replaceWikipediaTemplate(t, **kwargs): | ||
+ | if 'article' not in kwargs: | ||
+ | return t | ||
+ | if t.getName().lower() != 'w': | ||
+ | return t | ||
+ | link = u'[[w:' | ||
+ | if t.getParam('lang'): | ||
+ | link += t.getParam('lang') + u':' | ||
+ | link += t.getParam('1') + u'|' | ||
+ | if t.getParam('2'): | ||
+ | link += t.getParam('2') | ||
+ | else: | ||
+ | link += t.getParam('1') | ||
+ | link += u']]' | ||
+ | return link | ||
+ | addTemplateFilter(replaceWikipediaTemplate, lowPriority=True) | ||
=== Remove manual video IDs from {{tl|Weapon Demonstration}} === | === Remove manual video IDs from {{tl|Weapon Demonstration}} === | ||
Line 769: | Line 906: | ||
'patch layout': ['before', 'after', 'current'], | 'patch layout': ['before', 'after', 'current'], | ||
'item infobox': ['released'], | 'item infobox': ['released'], | ||
− | 'update history': [1] | + | 'update history': [1], |
+ | 'otherwikis': [1, 2] | ||
} | } | ||
if t.getName().lower() not in params: | if t.getName().lower() not in params: | ||
Line 784: | Line 922: | ||
'blueprint': ['ingredient-#n-local', 'result-local', 'result-#n-local'], | 'blueprint': ['ingredient-#n-local', 'result-local', 'result-#n-local'], | ||
'taunt': ['weapon-#n-local'], | 'taunt': ['weapon-#n-local'], | ||
+ | 'patch layout': ['diff-#n'], | ||
+ | 'item infobox': ['crafting-slot'], | ||
+ | 'model info': ['location'], | ||
+ | 'map infobox': ['map-stamp-link'] | ||
} | } | ||
if t.getName().lower() not in params: | if t.getName().lower() not in params: | ||
Line 795: | Line 937: | ||
t.delParam(p) | t.delParam(p) | ||
return t | return t | ||
− | addTemplateFilter(obsoleteParameterFilter | + | addTemplateFilter(obsoleteParameterFilter, lowPriority=True) |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
=== Add <code>day</code>/<code>month</code>/<code>year</code> to {{tl|Patch layout}} === | === Add <code>day</code>/<code>month</code>/<code>year</code> to {{tl|Patch layout}} === | ||
Line 855: | Line 960: | ||
self.subpageTemplateParam = <nowiki>"""{{#switch:{{{1|}}}|%options%}}"""</nowiki> | self.subpageTemplateParam = <nowiki>"""{{#switch:{{{1|}}}|%options%}}"""</nowiki> | ||
self.invalidParamError = <nowiki>"""<span class="error">Error: invalid param.</span>[[Category:ERROR]]"""</nowiki> | self.invalidParamError = <nowiki>"""<span class="error">Error: invalid param.</span>[[Category:ERROR]]"""</nowiki> | ||
+ | self.invalidKeyNameCharacters = <nowiki>"""#<>[]{}"""</nowiki> | ||
self.subpageTemplateID = <nowiki>"""%string%"""</nowiki> | self.subpageTemplateID = <nowiki>"""%string%"""</nowiki> | ||
+ | self.partialUpdateThreshold = 750 # Update SyncData every n edits | ||
self.dictionaries = { | self.dictionaries = { | ||
u'Template:Dictionary/items': { # Dictionary page | u'Template:Dictionary/items': { # Dictionary page | ||
Line 881: | Line 988: | ||
'name': 'merchandise', | 'name': 'merchandise', | ||
'sync': 'Template:Dictionary/merchandise/Special:SyncData' | 'sync': 'Template:Dictionary/merchandise/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/decorated': { | ||
+ | 'name': 'decorated', | ||
+ | 'sync': 'Template:Dictionary/decorated/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/descriptions': { | ||
+ | 'name': 'descriptions', | ||
+ | 'sync': 'Template:Dictionary/descriptions/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/dyk': { | ||
+ | 'name': 'dyk', | ||
+ | 'sync': 'Template:Dictionary/dyk/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/tournament medals': { | ||
+ | 'name': 'tournament medals', | ||
+ | 'sync': 'Template:Dictionary/tournament medals/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/attributes': { | ||
+ | 'name': 'attributes', | ||
+ | 'sync': 'Template:Dictionary/attributes/Special:SyncData' | ||
}, | }, | ||
u'Template:Dictionary/steam ids': { | u'Template:Dictionary/steam ids': { | ||
Line 956: | Line 1,083: | ||
u'Template:Dictionary/achievements/mann vs. machievements': { | u'Template:Dictionary/achievements/mann vs. machievements': { | ||
'name': 'achievements/mann vs. machievements', | 'name': 'achievements/mann vs. machievements', | ||
− | 'sync': 'Template:Dictionary/achievements/mann vs. | + | 'sync': 'Template:Dictionary/achievements/mann vs. machievements/Special:SyncData' |
+ | }, | ||
+ | u'Template:Dictionary/achievements/standin': { | ||
+ | 'name': 'achievements/standin', | ||
+ | 'sync': 'Template:Dictionary/achievements/standin/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/achievements/process': { | ||
+ | 'name': 'achievements/process', | ||
+ | 'sync': 'Template:Dictionary/achievements/process/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/achievements/snakewater': { | ||
+ | 'name': 'achievements/snakewater', | ||
+ | 'sync': 'Template:Dictionary/achievements/snakewater/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/achievements/powerhouse': { | ||
+ | 'name': 'achievements/powerhouse', | ||
+ | 'sync': 'Template:Dictionary/achievements/powerhouse/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/achievements/other games': { | ||
+ | 'name': 'achievements/other games', | ||
+ | 'sync': 'Template:Dictionary/achievements/other games/Special:SyncData' | ||
+ | }, | ||
+ | u'Template:Dictionary/achievements/pass time': { | ||
+ | 'name': 'achievements/pass time', | ||
+ | 'sync': 'Template:Dictionary/achievements/pass time/Special:SyncData' | ||
}, | }, | ||
u'Template:Dictionary/blueprints': { | u'Template:Dictionary/blueprints': { | ||
'name': 'blueprints', | 'name': 'blueprints', | ||
'sync': 'Template:Dictionary/blueprints/Special:SyncData' | 'sync': 'Template:Dictionary/blueprints/Special:SyncData' | ||
− | |||
− | |||
− | |||
− | |||
}, | }, | ||
u'Template:Dictionary/defindex': { | u'Template:Dictionary/defindex': { | ||
Line 975: | Line 1,122: | ||
'blankString': '-' | 'blankString': '-' | ||
}, | }, | ||
− | u'Template:Dictionary/ | + | u'Template:Dictionary/gameinfo': { |
− | 'name': ' | + | 'name': 'gameinfo', |
− | 'sync': 'Template:Dictionary/ | + | 'sync': 'Template:Dictionary/gameinfo/Special:SyncData' |
} | } | ||
} | } | ||
self.subpageSeparator = u'/' | self.subpageSeparator = u'/' | ||
# List of supported languages, in prefered order | # 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.languages = [u'en', u'ar', u'bg', 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'th', u'tr', u'uk', u'vi', u'zh-hans', u'zh-hant'] |
self.defaultLang = u'en' | self.defaultLang = u'en' | ||
self.allKeyName = u'_all_' | self.allKeyName = u'_all_' | ||
self.filterName = u'Your friendly neighborhood dictionary updater' | self.filterName = u'Your friendly neighborhood dictionary updater' | ||
self.commentsExtract = compileRegex(r'<!--([\S\s]+?)-->') | self.commentsExtract = compileRegex(r'<!--([\S\s]+?)-->') | ||
− | self. | + | self.subkeyName = compileRegex(r'^([-\w]+)$', re.IGNORECASE) |
− | |||
− | |||
addWhitelistPage(self.dictionaries.keys()) | addWhitelistPage(self.dictionaries.keys()) | ||
+ | self.editCounts = {} | ||
+ | def updateSyncData(self, currentDict, syncData, note=''): | ||
+ | # Build syncdata string representation | ||
+ | syncKeys = syncData.keys() | ||
+ | syncKeys.sort() | ||
+ | syncLines = [] | ||
+ | for k in syncKeys: | ||
+ | syncLines.append(k + u':' + syncData[k]) | ||
+ | if note: | ||
+ | note = u' (' + u(note) + u')' | ||
+ | editPage(self.dictionaries[currentDict]['sync'], u'\n'.join(syncLines), summary=<nowiki>u'Updated synchronization information for [[:' + currentDict + u']]' + note + u'.'</nowiki>, minor=True, nocreate=False) | ||
def generateSubpage(self, keyName, data, currentDict, syncData): | def generateSubpage(self, keyName, data, currentDict, syncData): | ||
h = hashlib.md5() | h = hashlib.md5() | ||
Line 1,047: | Line 1,203: | ||
if keyName in syncData and syncData[keyName] == h: | if keyName in syncData and syncData[keyName] == h: | ||
return # Same hash | return # Same hash | ||
− | |||
subpage = subpage.replace(u'%dictionary%', currentDict) | subpage = subpage.replace(u'%dictionary%', currentDict) | ||
subpage = subpage.replace(u'%dictionaryname%', self.dictionaries[currentDict]['name']) | subpage = subpage.replace(u'%dictionaryname%', self.dictionaries[currentDict]['name']) | ||
subpage = subpage.replace(u'%keyname%', keyName) | subpage = subpage.replace(u'%keyname%', keyName) | ||
− | + | if editPage(currentDict + self.subpageSeparator + keyName, subpage, summary=<nowiki>u'Pushed changes from [[:' + currentDict + u']] for string "' + keyName + u'".'</nowiki>, minor=True, nocreate=False): | |
+ | syncData[keyName] = h # Update sync data | ||
+ | if currentDict not in self.editCounts: | ||
+ | self.editCounts[currentDict] = 0 | ||
+ | self.editCounts[currentDict] += 1 | ||
+ | if self.editCounts[currentDict] > self.partialUpdateThreshold: | ||
+ | self.editCounts[currentDict] = 0 | ||
+ | self.updateSyncData(currentDict, syncData, 'Partial update') | ||
+ | def dedup(self, l): | ||
+ | s = set() | ||
+ | for i in l: | ||
+ | if i not in s: | ||
+ | s.add(i) | ||
+ | yield i | ||
def processComment(self, commentString, currentDict, definedStrings, syncData): | def processComment(self, commentString, currentDict, definedStrings, syncData): | ||
commentContents = [] | commentContents = [] | ||
− | for | + | commentString = u(commentString).replace(u'\r', u'') |
− | + | parseState = { | |
− | if | + | 'currentKeys': [], |
− | + | 'currentSubkeys': {}, | |
− | + | 'currentKeyIsValid': False, | |
− | if | + | 'abortCurrentSubkeys': False, |
− | + | 'subKeyLines': {}, | |
− | + | } | |
− | + | subPageData = {} | |
− | + | def finalize(): | |
− | + | if parseState['currentKeyIsValid']: | |
− | + | # End processing of current set of subkeys. | |
− | + | if not parseState['abortCurrentSubkeys']: | |
− | + | isTranslation = True | |
− | + | for k in parseState['currentKeys']: | |
− | + | assert k not in subPageData, 'Internal logic consistency error: duplicate key %r' % (k,) | |
− | + | subPageData[k] = {} | |
− | for | + | for subKey, data in parseState['currentSubkeys'].items(): |
− | if | + | isTranslation = isTranslation and subKey in self.languages |
− | + | subPageData[k][subKey] = data | |
− | + | if isTranslation: | |
− | + | for lang in self.languages: | |
− | + | if lang in parseState['subKeyLines']: | |
− | for | + | commentContents.append(parseState['subKeyLines'][lang]) |
− | + | else: | |
− | + | for subKey in sorted(parseState['subKeyLines'].keys()): | |
− | + | commentContents.append(parseState['subKeyLines'][subKey]) | |
− | + | parseState['abortCurrentSubkeys'] = False | |
− | for | + | parseState['currentKeyIsValid'] = False |
− | + | for line in commentString.split(u'\n'): | |
− | if | + | if u'WINDBOT_INVALID' in line: |
− | + | commentContents.append(line) | |
− | + | continue | |
− | + | def badLine(reason): | |
− | + | if parseState['abortCurrentSubkeys']: | |
− | if | + | for k in sorted(parseState['subKeyLines'].keys()): |
− | + | commentContents.append(parseState['subKeyLines'][k] + u' // WINDBOT_INVALID Other subkeys for this key are invalid') | |
− | self. | + | parseState['subKeyLines'] = {} |
− | return u' | + | if parseState['currentKeyIsValid'] and not parseState['abortCurrentSubkeys']: |
+ | parseState['abortCurrentSubkeys'] = True | ||
+ | else: | ||
+ | parseState['currentKeyIsValid'] = False | ||
+ | commentContents.append(line + u' // WINDBOT_INVALID ' + u(reason.replace(u':', ' '))) | ||
+ | if line.strip() == u'': | ||
+ | finalize() | ||
+ | commentContents.append(line) | ||
+ | continue | ||
+ | if line.strip()[0] == u'#': # Human comment | ||
+ | commentContents.append(line) | ||
+ | continue | ||
+ | if line[0] not in (u' ', '\t'): # Key, or key + no-subkey data | ||
+ | if line.find(u':') == -1: # Colon was probably forgotten | ||
+ | badLine('Maybe a forgotten colon?') | ||
+ | continue | ||
+ | if parseState['currentKeyIsValid']: | ||
+ | badLine('Missing linebreak before new key?') | ||
+ | continue | ||
+ | beforeColon, afterColon = line.split(u':', 1) | ||
+ | afterColon = afterColon.strip() | ||
+ | # Check keys. | ||
+ | keyNames = [k.replace(u'_', u' ').replace(u'#', u'').strip().lower() for k in beforeColon.strip().split(u'|')] | ||
+ | keyNames = [k for k in self.dedup(keyNames) if k] | ||
+ | if len(keyNames) == 0: | ||
+ | badLine('No valid key names') | ||
+ | continue | ||
+ | duplicateKey = None | ||
+ | badKey = None | ||
+ | for k in keyNames: | ||
+ | for c in self.invalidKeyNameCharacters: | ||
+ | if c in k: | ||
+ | badKey = k | ||
+ | break | ||
+ | if k in definedStrings: | ||
+ | duplicateKey = k | ||
+ | break | ||
+ | if k in subPageData: | ||
+ | duplicateKey = k | ||
+ | break | ||
+ | if duplicateKey: | ||
+ | badLine('Duplicate key: %r' % duplicateKey) | ||
+ | continue | ||
+ | if badKey: | ||
+ | badLine('Key has invalid characters: %r' % badKey) | ||
+ | continue | ||
+ | # Key looks good. | ||
+ | for k in keyNames: | ||
+ | definedStrings.add(k) | ||
+ | # Check for no-subkey data. | ||
+ | if afterColon: | ||
+ | for k in keyNames: | ||
+ | subPageData[k] = afterColon | ||
+ | commentContents.append(u' | '.join(keyNames) + u': ' + afterColon) | ||
+ | else: | ||
+ | parseState['currentKeyIsValid'] = True | ||
+ | parseState['abortCurrentSubkeys'] = False | ||
+ | parseState['currentKeys'] = keyNames | ||
+ | parseState['currentSubkeys'] = {} | ||
+ | parseState['subKeyLines'] = {} | ||
+ | commentContents.append(u' | '.join(keyNames) + u':') | ||
+ | continue | ||
+ | # Sub-key definition follows (has a space as first character). | ||
+ | if parseState['abortCurrentSubkeys']: | ||
+ | commentContents.append(line) | ||
+ | continue | ||
+ | if not parseState['currentKeyIsValid']: | ||
+ | badLine('Sub-key being defined despite no valid key') | ||
+ | continue | ||
+ | if line.find(u':') == -1: # Colon was probably forgotten | ||
+ | badLine('Missing colon in subkey definition') | ||
+ | continue | ||
+ | beforeColon, afterColon = line.strip().split(u':', 1) | ||
+ | subKeyNames = [k.strip().lower() for k in beforeColon.strip().split(u'|')] | ||
+ | subKeyNames = [k for k in self.dedup(subKeyNames) if k] | ||
+ | badSubkeyName = None | ||
+ | duplicateSubkey = None | ||
+ | for k in subKeyNames: | ||
+ | if not self.subkeyName.match(k): | ||
+ | badSubkeyName = k | ||
+ | break | ||
+ | if k in parseState['currentSubkeys'].keys(): | ||
+ | duplicateSubkey = k | ||
+ | break | ||
+ | if badSubkeyName: | ||
+ | badLine('Invalid subkey name: %r' % badSubkeyName) | ||
+ | continue | ||
+ | if duplicateSubkey: | ||
+ | badLine('Duplicate subkey name: %r' % duplicateSubkey) | ||
+ | continue | ||
+ | subKeyData = afterColon.strip() | ||
+ | if not subKeyData: | ||
+ | badLine('Empty data') | ||
+ | continue | ||
+ | for k in subKeyNames: | ||
+ | parseState['currentSubkeys'][k] = subKeyData | ||
+ | parseState['subKeyLines'][k] = u' ' + k + u': ' + subKeyData | ||
+ | finalize() | ||
+ | for k, data in subPageData.items(): | ||
+ | self.generateSubpage(k, data, currentDict, syncData) | ||
+ | return u'\n'.join(commentContents) | ||
def __call__(self, content, **kwargs): | def __call__(self, content, **kwargs): | ||
if 'article' not in kwargs: | if 'article' not in kwargs: | ||
Line 1,099: | Line 1,367: | ||
return content | return content | ||
currentDict = u(kwargs['article'].title) | currentDict = u(kwargs['article'].title) | ||
− | + | if random.randint(0, 50) == 0: # With probability 2%, ignore syncdata completely. Helps with stale syncdata and people overwriting things. | |
− | |||
− | |||
− | |||
syncDataText = u'' | syncDataText = u'' | ||
+ | else: | ||
+ | try: | ||
+ | syncDataText = u(page(self.dictionaries[currentDict]['sync']).getWikiText()).split(u'\n') | ||
+ | except: # Page probably doesn't exist | ||
+ | syncDataText = u'' | ||
syncData = {} | syncData = {} | ||
for sync in syncDataText: | for sync in syncDataText: | ||
Line 1,109: | Line 1,379: | ||
if not sync: | if not sync: | ||
continue | continue | ||
− | sync = sync.split(u':', | + | sync = sync.split(u':', 1) |
if len(sync) == 2: | if len(sync) == 2: | ||
syncData[sync[0]] = sync[1] | syncData[sync[0]] = sync[1] | ||
Line 1,115: | Line 1,385: | ||
newContent = u'' | newContent = u'' | ||
previousIndex = 0 | previousIndex = 0 | ||
− | definedStrings = | + | definedStrings = set() |
for comment in self.commentsExtract.finditer(content): | for comment in self.commentsExtract.finditer(content): | ||
newContent += content[previousIndex:comment.start()] | newContent += content[previousIndex:comment.start()] | ||
previousIndex = comment.end() | previousIndex = comment.end() | ||
# Process current comment | # Process current comment | ||
− | newContent += u'<!--\n\n' + self.processComment(u(comment.group(1)).strip(), currentDict, definedStrings, syncData) + u'\n\n-->' | + | newContent += <nowiki>u'<!--\n\n' + self.processComment(u(comment.group(1)).strip(), currentDict, definedStrings, syncData) + u'\n\n-->'</nowiki> |
newContent += content[previousIndex:] | newContent += content[previousIndex:] | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
# Check for deleted strings | # Check for deleted strings | ||
for k in oldSyncData: | for k in oldSyncData: | ||
Line 1,137: | Line 1,401: | ||
if k in syncData: | if k in syncData: | ||
del syncData[k] | del syncData[k] | ||
− | + | self.updateSyncData(currentDict, syncData, 'Full update') | |
− | + | self.editCounts[currentDict] = 0 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
return newContent | return newContent | ||
def scheduledRun(self): | def scheduledRun(self): | ||
Line 1,153: | Line 1,410: | ||
addFilter(dictUpdater) | addFilter(dictUpdater) | ||
scheduleTask(dictUpdater.scheduledRun, 3) | scheduleTask(dictUpdater.scheduledRun, 3) | ||
+ | |||
+ | === Remove some templates on translated pages if they are absent on english page === | ||
+ | def autoRemoveRecentAdditionTag(t, **kwargs): | ||
+ | # "stub" is included here because translators often copy this template from the English page | ||
+ | # instead of replacing it with "trans" or "update trans". | ||
+ | # This wiki is English first, and as such, stub usage in translated pages is incorrect. | ||
+ | # In the future, it might be more useful to just replace "stub" with "trans" or "update trans" instead. | ||
+ | # - T 01/08/24 | ||
+ | toRemoveTemplates = ('recent addition', 'ra', 'new item', 'unreleased', 'beta', 'stub') | ||
+ | equivalences = { | ||
+ | 'ra': 'recent addition', | ||
+ | 'new item': 'recent addition' | ||
+ | } | ||
+ | if t.getName().lower() not in toRemoveTemplates or 'article' not in kwargs or '/' not in kwargs['article'].title: | ||
+ | return t | ||
+ | targetTemplate = t.getName().lower() | ||
+ | if targetTemplate in equivalences: | ||
+ | targetTemplate = equivalences[targetTemplate] | ||
+ | englishArticle = page(kwargs['article'].title[:kwargs['article'].title.find('/')]) | ||
+ | try: | ||
+ | englishContent = englishArticle.getWikiText() | ||
+ | englishContent, englishTemplates, englishKeys = templateExtract(englishContent) | ||
+ | for t2 in englishTemplates.values(): | ||
+ | if t2.getName().lower() in toRemoveTemplates: | ||
+ | foundTemplate = t2.getName().lower() | ||
+ | if foundTemplate in equivalences: | ||
+ | foundTemplate = equivalences[foundTemplate] | ||
+ | if foundTemplate == targetTemplate: | ||
+ | return t | ||
+ | return None | ||
+ | except: | ||
+ | return t | ||
+ | addTemplateFilter(autoRemoveRecentAdditionTag) | ||
== File filters == | == File filters == | ||
Line 1,159: | Line 1,449: | ||
== Additional tasks == | == Additional tasks == | ||
− | === Update {{tl| | + | === Update {{tl|Dictionary/defindex}} and {{tl|Dictionary/price}} === |
− | + | '''TEMPORARILY DISABLED:''' WindBOT can't seem to properly fetch the item schema (probably an issue in steamGetGameSchema()), which causes this function to fail. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | [[User:PhoneWave|PhoneWave]] is taking care of this for now. See [[User:PhoneWave/forceDictionary/defindex]]. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
def updateGameDictionaries(): | def updateGameDictionaries(): | ||
if steam is None: | if steam is None: | ||
return | return | ||
# Begin configurable section | # Begin configurable section | ||
− | game = | + | game = 440 |
priceDictionaryPage = u'Template:Dictionary/price' | priceDictionaryPage = u'Template:Dictionary/price' | ||
indexDictionaryPage = u'Template:Dictionary/defindex' | indexDictionaryPage = u'Template:Dictionary/defindex' | ||
itemEquivalences = { | itemEquivalences = { | ||
− | u'taunt: the | + | u'taunt: the (.*)': u'\\1', |
− | u'taunt: | + | u'taunt: (.*)': u'\\1', |
− | u'taunt: the | + | u'taunt: the (.*)': u'\\1 (taunt)', # Useful for Meet the Medic |
− | u'taunt: | + | u'taunt: (.*)': u'\\1 (taunt)', # Useful for Meet the Medic |
+ | u'strange filter: (.*)': u'strange filter - \\1' | ||
} | } | ||
forceDefindex = { | forceDefindex = { | ||
− | u' | + | u'champ stamp': 815, |
+ | u'confidential collection case': 5823, | ||
+ | u'fall 2013 acorns crate key': 5710, | ||
+ | u'flying guillotine': 812, | ||
+ | u'gargoyle key': 5827, | ||
+ | u'gloves of running urgently': 239, | ||
+ | u'human cannonball': 817, | ||
u'huo-long heater': 811, | u'huo-long heater': 811, | ||
− | u' | + | u'lugermorph': 160, |
+ | u'mann co. stockpile crate': 5738, | ||
+ | u'mann co. stockpile crate key': 5740, | ||
+ | u'mann co. store package': 729, | ||
+ | u'mann co. strongbox key': 5720, | ||
+ | u'mann co. supply crate key': 5021, | ||
+ | u'marxman': 816, | ||
u'neon annihilator': 813, | u'neon annihilator': 813, | ||
+ | u'pda': 737, | ||
+ | u'pyrovision goggles': 743, | ||
+ | u'quarantined collection case': 5822, | ||
+ | u'red-tape recorder': 810, | ||
+ | u'something special for someone special': 5074, | ||
u'triad trinket': 814, | u'triad trinket': 814, | ||
− | u' | + | u'what\'s in the team fortress 2 soundtrack box?': 1176, |
− | u' | + | u'love and war cosmetics bundle': 2131, |
− | |||
} | } | ||
− | illegalItemCharacters = compileRegex(r'[:%\r\n\t]') | + | decimalSeparator = <nowiki>'{{dec}}'</nowiki> |
− | <nowiki> | + | illegalItemCharacters = compileRegex(<nowiki>r'[:%#\r\n\t]+'</nowiki>) |
− | indexDictionaryHeader = u'{{dictionary/header}}\n== /defindex ==\n\'\'\'This dictionary is automatically updated by WindBOT\'\'\'. Edits made to this page will be overwritten.\n<!--\n' | + | priceDictionaryHeader = <nowiki>u'{{dictionary/header}}\n== /price ==\n\'\'\'This dictionary is automatically updated by WindBOT\'\'\'. Edits made to this page will be overwritten.\n<!--\n'</nowiki> |
− | dictionaryFooter = u'\n-->'</nowiki> | + | indexDictionaryHeader = <nowiki>u'{{dictionary/header}}\n== /defindex ==\n\'\'\'This dictionary is automatically updated by WindBOT\'\'\'. Edits made to this page will be overwritten.\n<!--\n'</nowiki> |
+ | dictionaryFooter = <nowiki>u'\n-->'</nowiki> | ||
# End configurable section | # End configurable section | ||
schema = steamGetGameSchema(game) | schema = steamGetGameSchema(game) | ||
Line 1,318: | Line 1,505: | ||
defindex = {} | defindex = {} | ||
for item in schema: | for item in schema: | ||
− | + | itemName = u(item.name).lower().replace(u'_', u' ') | |
− | + | keys = [itemName] | |
− | + | for r in itemEquivalences: | |
− | + | itemName = re.sub(u'^' + r + u'$', itemEquivalences[r], itemName) | |
− | + | if itemName not in keys: | |
keys.append(itemName) | keys.append(itemName) | ||
− | + | itemKeys = u' | '.join([illegalItemCharacters.sub(u<nowiki>''</nowiki>, x) for x in keys]) | |
− | + | defindex[itemName] = itemKeys + u': ' + u(item.schema_id) | |
− | + | if item not in assets: | |
− | + | continue | |
− | + | prices = assets[item.schema_id].price | |
− | + | if not len(prices): | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
continue | continue | ||
+ | prices = dict((u(k).strip().lower(), v) for k, v in prices.iteritems()) # Sanitize data | ||
+ | dictionary[itemName] = itemKeys + u':' | ||
+ | for currency in sorted(prices): | ||
+ | price = prices[currency] | ||
+ | if price.is_integer(): | ||
+ | price = u(int(price)) | ||
+ | else: | ||
+ | price = u(round(price, 2)) | ||
+ | dot = price.find(u'.') | ||
+ | if dot == len(price) - 2: | ||
+ | price += u'0' | ||
+ | dictionary[itemName] += u'\n ' + u(currency).lower() + u': ' + price.replace('.', decimalSeparator) | ||
for item in forceDefindex: | for item in forceDefindex: | ||
− | defindex[u(item)] = illegalItemCharacters.sub(u'', u(item)) + u': ' + u(forceDefindex[item]) | + | defindex[u(item)] = illegalItemCharacters.sub(u<nowiki>''</nowiki>, u(item)) + u': ' + u(forceDefindex[item]) |
if len(dictionary): | if len(dictionary): | ||
# Is updating time doktor | # Is updating time doktor | ||
Line 1,363: | Line 1,552: | ||
# Run other filters on the new page | # Run other filters on the new page | ||
fixPage(indexDictionaryPage) | fixPage(indexDictionaryPage) | ||
− | scheduleTask(updateGameDictionaries, 2) | + | # scheduleTask(updateGameDictionaries, 2) |
+ | |||
+ | === Update [[Lastpatch]] and [[Lastpatchbeta]] === | ||
+ | # Redirect Updater Class - modified from <nowiki>http://github.com/i-ghost/wikiscripts</nowiki> | ||
+ | # To update the lastpatch redirects on Team Fortress Wiki, but could probably be deployed on The Portal Wiki/Dota 2 Wiki | ||
+ | # i-ghost | ||
+ | |||
+ | class redirectUpdater(object): | ||
+ | def __init__(self, updateTemplateName="Template:Updates", pageName="Lastpatch", betaPageName="Lastpatchbeta"): | ||
+ | self.pageName = pageName | ||
+ | self.betaPageName = betaPageName | ||
+ | self.updateTemplateName = updateTemplateName | ||
+ | self.pageText = page(self.pageName).getWikiText() | ||
+ | self.betaPageText = page(self.betaPageName).getWikiText() | ||
+ | self.updateTemplateText = page(self.updateTemplateName).getWikiText().split("\n") | ||
+ | self.langs = ["ru", "fr", "de", "pl", "pt-br", "fi", "es", "nl", "zh-hans", "zh-hant", "ar", "cs", "da", "hu", "it", "ja", "ko", "no", "pt", "ro", "sv", "tr"] | ||
+ | self._get_dates() | ||
+ | self.patch = "%s %s, %s" % (self._get_month(int(self.updates["patch-month"])), self.updates["patch-day"], self.updates["patch-year"]) | ||
+ | self.betaPatch = "%s %s, %s" % (self._get_month(int(self.updates["patch-beta-month"])), self.updates["patch-beta-day"], self.updates["patch-beta-year"]) | ||
+ | self.footer = <nowiki>"<!-- This page is automatically generated when %s is modifed. Do not modify this page. -->"</nowiki> % (self.updateTemplateName) | ||
+ | |||
+ | def _get_month(self, month): | ||
+ | """WindBOT doesn't import calendar, so we use this instead""" | ||
+ | return datetime.date(1960, month, 1).strftime("%B") | ||
+ | |||
+ | def _get_dates(self): | ||
+ | """Internal: Gets dates from update template and stores in dictionary""" | ||
+ | self.updates = {} | ||
+ | for i in self.updateTemplateText: | ||
+ | if i.find("|") == 0: | ||
+ | # Liable to break | ||
+ | self.updates[i.lstrip("|").replace(" ", "").partition("=")[0]] = i.replace(" ", "").partition("=")[2].replace(<nowiki>"<!--Don'tforgettoupdateme!-->"</nowiki>, "") | ||
+ | |||
+ | def make_edit_strings(self, beta=False, lang=False): | ||
+ | """Creates the final page content.""" | ||
+ | <nowiki>if beta and lang: | ||
+ | self.redirect_string = "#REDIRECT [[%s Patch (Beta)/%s]] {{R lang|%s}}" % (self.betaPatch, lang, lang) | ||
+ | self.summary_string = "Updated [[%s/%s]] redirect to [[%s Patch (Beta)/%s]]" % (self.betaPageName, lang, self.betaPatch, lang) | ||
+ | elif beta and not lang: | ||
+ | self.redirect_string = "#REDIRECT [[%s Patch (Beta)]]" % (self.betaPatch) | ||
+ | self.summary_string = "Updated [[%s]] redirect to [[%s Patch (Beta)]]" % (self.betaPageName, self.betaPatch) | ||
+ | |||
+ | if lang and not beta: | ||
+ | self.redirect_string = "#REDIRECT [[%s Patch/%s]] {{R lang|%s}}" % (self.patch, lang, lang) | ||
+ | self.summary_string = "Updated [[%s/%s]] redirect to [[%s Patch/%s]]" % (self.pageName, lang, self.patch, lang) | ||
+ | elif not lang and not beta: | ||
+ | self.redirect_string = "#REDIRECT [[%s Patch]]" % (self.patch) | ||
+ | self.summary_string = "Updated [[%s]] redirect to [[%s Patch]]" % (self.pageName, self.patch)</nowiki> | ||
+ | |||
+ | def _update_redirect(self, beta=False, lang=False): | ||
+ | """Internal: Provides editing functionality""" | ||
+ | self.make_edit_strings(beta, lang) | ||
+ | # Use the correct page | ||
+ | if beta: | ||
+ | if lang: | ||
+ | _pagetoedit = "%s/%s" % (self.betaPageName, lang) | ||
+ | else: | ||
+ | _pagetoedit = self.betaPageName | ||
+ | else: | ||
+ | if lang: | ||
+ | _pagetoedit = "%s/%s" % (self.pageName, lang) | ||
+ | else: | ||
+ | _pagetoedit = self.pageName | ||
+ | # Send the edit | ||
+ | editPage(_pagetoedit, "%s\n%s" % (self.redirect_string, self.footer), summary=self.summary_string, minor=True, nocreate=False) | ||
+ | |||
+ | def check_if_update_needed(self, beta=False): | ||
+ | """Checks if redirects needs updating""" | ||
+ | try: | ||
+ | if beta: | ||
+ | if self.betaPageText.split("[[")[1].partition("]]")[0].rstrip("Patch (Beta)") != self.betaPatch: return True | ||
+ | else: | ||
+ | if self.pageText.split("[[")[1].partition("]]")[0].rstrip(" Patch") != self.patch: return True | ||
+ | except IndexError: | ||
+ | return True # Just update anyway | ||
+ | |||
+ | def update(self, beta=False): | ||
+ | """Updates the redirects and their lang pages""" | ||
+ | if beta: | ||
+ | self._update_redirect(beta=True) | ||
+ | for lang in self.langs: | ||
+ | self._update_redirect(beta=True, lang=lang) | ||
+ | else: | ||
+ | self._update_redirect() | ||
+ | for lang in self.langs: | ||
+ | self._update_redirect(lang=lang) | ||
+ | |||
+ | def run(self): | ||
+ | """Runs everything""" | ||
+ | # Beta | ||
+ | if self.check_if_update_needed(beta=True): | ||
+ | self.update(beta=True) | ||
+ | # Normal | ||
+ | if self.check_if_update_needed(): | ||
+ | self.update() | ||
+ | |||
+ | scheduleTask(redirectUpdater().run, 2) | ||
+ | |||
+ | === Update game prices === | ||
+ | Delegated to [[User:PhoneWave|PhoneWave]]. See [[User:PhoneWave/forceDictionary/gameinfo]] for gameids. | ||
+ | |||
+ | === Update {{tl|Dictionary/steam ids}} === | ||
+ | |||
+ | def updatePlayerInfoDictionary(): # By Smashman | ||
+ | """A WindBOT filter created to fetch Steam user's name and vanity information and store it in Template:Dictionary/steam_ids""" | ||
+ | # Begin configurable section | ||
+ | <nowiki>playerInfoDictionaryHeader = u'{{dictionary/header}}\n== /steam_ids ==\n\'\'\'The information in this dictionary is automatically updated by WindBOT. Add new Steam IDs in [[Template:Dictionary/steam ids/id list]]\'\'\'.\n<!--\n' | ||
+ | playerListDictionaryHeader = u'{{dictionary/header}}\n== /steam_ids/id_list ==\n\'\'\'The information in this dictionary is tracked by WindBOT. Add new Steam IDs to the bottom of this list\'\'\'.\n\n' | ||
+ | playerInfoDictionaryFooter = u'\n-->'</nowiki> | ||
+ | # End configurable section | ||
+ | # Is updating time doktor | ||
+ | playerInfoDictionaryPage = u'Template:Dictionary/steam ids' | ||
+ | playerListDictionaryPage = u'Template:Dictionary/steam ids/id list' | ||
+ | id_list = page('Template:Dictionary/steam ids/id list').getWikiText() | ||
+ | id64s=sorted(frozenset(filter(lambda x: x.isdigit(), id_list.splitlines()))) | ||
+ | listPage = playerListDictionaryHeader + '\n'.join(map(str, id64s)) + '\n' | ||
+ | editPage(playerListDictionaryPage, listPage, summary=u'Sort tracked Steam IDs.', minor=True, bot=True, nocreate=True) | ||
+ | start_limit = 0 | ||
+ | end_limit = 100 | ||
+ | i=0 | ||
+ | div_100 = (len(id64s)/100)+1 #Times to repeat sections | ||
+ | finalPage = playerInfoDictionaryHeader | ||
+ | while i != div_100: | ||
+ | id64_sort = sorted(id64s[start_limit:end_limit]) #Numerically sort the ids, for use in call | ||
+ | id64s_csv = ','.join(id64_sort) #Put them into a csv string | ||
+ | data = steam.api.interface("ISteamUser").GetPlayerSummaries(version = 2, steamids = id64s_csv) #Do the call | ||
+ | data = sorted(data["response"]["players"], key=lambda x: x['steamid']) #Numerically sort by id | ||
+ | for player in data: | ||
+ | purl = player["profileurl"].strip('/') | ||
+ | finalPage += u"\n{0}:".format(player["steamid"]) | ||
+ | <nowiki>finalPage += u("\n nickname: <no" + u"wiki>{0}</no" + u"wiki>").format(player["personaname"].replace(u'<!--', u'<!--').replace(u'-->', u'-->'))</nowiki> | ||
+ | if purl.find("/id/") != -1: | ||
+ | vanity = os.path.basename(purl) | ||
+ | finalPage += u"\n vanity: {0}".format(vanity) | ||
+ | finalPage += u"\n" | ||
+ | start_limit += 100 | ||
+ | end_limit += 100 | ||
+ | i+=1 | ||
+ | finalPage += playerInfoDictionaryFooter | ||
+ | editPage(playerInfoDictionaryPage, finalPage, summary=u'Updated steam player information from WebAPI.', minor=True, bot=True, nocreate=True) | ||
+ | fixPage(playerInfoDictionaryPage) | ||
+ | scheduleTask(updatePlayerInfoDictionary, 16) | ||
+ | def updateDictionaryOnSteamIdListUpdate(content, **kwargs): | ||
+ | if 'article' in kwargs and kwargs['article'] == 'Template:Dictionary/steam ids/id list': | ||
+ | updatePlayerInfoDictionary() | ||
+ | return content | ||
+ | addFilter(updateDictionaryOnSteamIdListUpdate) |
Latest revision as of 16:21, 7 December 2024
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.
Contents
- 1 How to disable a filter
- 2 Global filters
- 3 Page filters
- 4 Semantic filters
- 5 Language-specific filters
- 6 Link filters
- 7 Template filters
- 7.1 Template renaming
- 7.2 Lang template renaming
- 7.3 Reindent all infoboxes
- 7.4 Manage all {{Item infobox}}es
- 7.5 Remove useless templates
- 7.6 Replace Wikipedia link template with interwiki link
- 7.7 Remove manual video IDs from {{Weapon Demonstration}}
- 7.8 Filter parameters of certain templates
- 7.9 Remove obsolete parameters
- 7.10 Add day/month/year to {{Patch layout}}
- 7.11 Implement {{Dictionary}}
- 7.12 Remove some templates on translated pages if they are absent on english page
- 8 File filters
- 9 Additional tasks
Global filters
File categorization (Add {{No category}}
or {{User image}}
)
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}}' userImageCategory = u'[[Category:User images]]' userAudioCategory = u'[[Category:User audio]]' userTextCategory = u'[[Category:User text]]' regLang = compileRegex('/[^/]+$') if 'article' not in kwargs or content.lower().find(u'#redirect') != -1: return content title = u(kwargs['article'].title) if title[:5].lower() != u'file:': return content categories = kwargs['article'].getCategories() if title.lower().startswith(u'file:user '): if title.lower().endswith(('.mp3', '.wav', '.ogg', '.flac')) and u'Category:User audio' not in categories and userAudioCategory not in content: return (content.strip() + u'\n' + userAudioCategory).strip() if title.lower().endswith(('.txt', '.cfg', '.diff', '.patch')) and u'Category:User text' not in categories and userTextCategory not in content: return (content.strip() + u'\n' + userTextCategory).strip() if title.lower().endswith(('.png', '.gif', '.jpg', '.jpeg', '.webp', '.apng', '.psd', '.webm')) and u'Category:User images' not in categories and userImageCategory not in content: return (content.strip() + u'\n' + userImageCategory).strip() return content # Don't go further for c in categories: 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|module):')
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', 'Miscellaneous items', 'Buildings', 'Item levels', 'Developer weapons', 'Self-made items', 'Unused content', 'Promotional items', 'Mercenary', 'Taunts', 'Decapitation', 'Fists', 'Cloak', 'One-Man Army', # Too common to be reliably replaced 'Club', 'Bat', 'Knife', 'Syringe', 'Bottle', 'Original', # Ditto (weapons) 'Classified', # Not capitalized even when referring to the hat 'Attendant', # Avoids issue with French 'Buff Banner', 'Medi Gun', 'Bonk Helm', 'B.A.S.E. Jumper', # Avoids issue with German 'Voodoo Juju', # Conflicts Juju/JuJu 'Reskins' # Not capitalized ] 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('an Sapper', 'a Sapper'), dumbReplace('An Sapper', 'A 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
Other capitalized words
enforceCapitalization('Team Fortress 2', 'TF2') enforceCapitalization('Payload', 'Overtime', 'Dispensers', 'Sappers', 'Crits', 'Crit') enforceCapitalization('PlayStation', 'Xbox') enforceCapitalization('iPod', 'iPhone')
Word aliases
addSafeFilter( wordFilter(u'Ubersaw', u'[üÜ]bersaw'), # Ubersaw wordFilter(u'Force-a-Nature', u'Force of Nature'), # Force-A-Nature 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'Sapper', u'(?:Ele[ck]tro |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'Über Update', u'[UÜü]ber update') #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=['fr', 'de', 'pl'] )
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('Flame Thrower', 'Flamethrower'), wordFilter('Mini-Crits', 'Minicrits'), wordFilter('Mini-Crit', 'Mini-crit'), wordFilter('Mini-Crits', 'Mini-crits'), wordFilter('Critical hit', 'Critical Hit'), wordFilter('Critical hits', 'Critical Hits'), wordFilter('Chargin\' Targe', 'charg[ei][-\'n\\s]*targe?'), wordFilter('Kritzkrieg', 'Kritzkreig'), wordFilter('screenshot', 'screen shot'), wordFilter('screenshots', 'screen shots'), wordFilter('in-game', 'ingame'), # wordFilter('team-colored', 'team colou?red', keepcapitalization=True) # wordFilter('color', '(?<!Rustic )colour') ) addSafeFilter( wordFilter('Natascha', 'Natas?c?ha'), language='en' ) addSafeFilter( wordFilter(u'tradable', u'tradeable', keepcapitalization=True), language='en' ) addSafeFilter( wordFilter('Spies', 'Spys'), languageBlacklist=['de', 'es'] )
Map names
addSafeFilter( #wordFilter('Gravel Pit', '(?<!cp_)Gravelpit', 'Gravel pit'), - Conflicts with Gravelpit Emperor and its various translations 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) ?==+'), wordFilter(u'== See also ==', u'==+ ?See also ?==+'), wordFilter(u'== External links ==', u'==+ ?External links ?==+'), wordFilter(u'== Painted variants ==', u'==+ ?Painted variants ?==+'), wordFilter(u'== Item set ==', u'==+ ?Item set ?==+'), wordFilter(u'== Damage and function times ==', u'==+ ?Damage and function times ?==+'), wordFilter(u'=== As a crafting ingredient ===', u'==+ ?As a crafting ingredient ?==+'), wordFilter(u'== Unused content ==', u'==+ ?Unused content ?==+'), wordFilter(u'== See also ==', u'==+ ?See also ?==+'), wordFilter(u'== Related achievements ==', u'==+ ?Related achievements ?==+'), wordFilter(u'== Strange variant ==', u'==+ ?Strange variant ?==+'), wordFilter(u'=== Undocumented changes ===', u'==+ ?Undocumented changes ?==+') )
Language-specific filters
Language-agnostic
# 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( # Requested by Picard wordFilter(u'Krit-a-Cola', u'(?<!Bonk! )[CK]rit-\'?[an]\'?-Cola'), wordFilter(u'Bonk! Krit-\'n-Cola', u'Bonk! [CK]rit-\'?[an]\'?-Cola'), wordFilter(u'Krit', u'Crit'), wordFilter(u'Krits', u'Crits'), wordFilter(u'Sonstiger Gegenstand', u'Diverser Gegenstand', u'diverser Gegenstand', u'sonstiger Gegenstand'), wordFilter(u'Sonstige Gegenstände', u'Diverse Gegenstände', 'diverse Gegenstände', 'sonstige Gegenstände'), 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'(?<!Rifle de )Francotirador'), wordFilter(u'Snipers', u'(?<!Rifle de )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'Übersaw', u'Ubersaw'), wordFilter(u'Artículo', u'Objeto', keepcapitalization=True), wordFilter(u'Artículos', u'Objetos', keepcapitalization=True), 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, and later updated as requested by Ashe wordFilter(u'== Variaciones de color ==', 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' )
addSafeFilter( # Requested by Jagoba RL, then flipped by request of Dio wordFilter(u'SuperActualización', u'Actualizaci[oó]n [Üüu]ber'), language='es' )
def spanishNivelCapitalizationFilter(t, **kwargs): # Capitalize "nivel" inside {{item infobox}} and {{backpack item}} templates, requested by rZ if t.getName().lower() == 'item infobox' and t.getParam('level'): t.setParam('level', t.getParam('level').replace('nivel', 'Nivel')) if t.getName().lower() == 'backpack item' and t.getParam('item-level'): t.setParam('item-level', t.getParam('item-level').replace('nivel', 'Nivel')) return t addTemplateFilter(spanishNivelCapitalizationFilter, 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 Cructo if t.getName().lower() == u'item infobox': return t # Skip if t.getParam('availability'): t.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( # General wordFilter(u'Fabricação', u'Crafting', keepcapitalization=True), wordFilter(u'Conquista', u'Achievement', keepcapitalization=True), wordFilter(u'Conquistas', u'Achievements', keepcapitalization=True), wordFilter(u'Demomen', u'Demomans'), wordFilter(u'Heavies', u'Heavys'), wordFilter(u'N/D', u'N/A'), wordFilter(u'Atualização', u'Patch', keepcapitalization=True), wordFilter(u'Atualizações', u'Patches', keepcapitalization=True), wordFilter(u'Desempenho', u'Performance', keepcapitalization=True), wordFilter(u'Spies', u'Spys'), wordFilter(u'ÜberCarga', u'Sobrecarga', u'Übercharge', u'ÜberCharge'), wordFilter(u'Mochila', u'Backpack', u'Inventário', keepcapitalization=True), wordFilter(u'Comunidade Steam', u'Steam Community'), wordFilter(u'== Bugs ==', u'==+ *Defeitos *==+'), wordFilter(u'Sala de renascimento', u'Respawn room', u'Sala de respawn', keepcapitalization=True), wordFilter(u'Área de renascimento', u'Respawn area', u'Área de respawn', keepcapitalization=True), wordFilter(u'Über Atualização', u'Atualização Über', u'Atualização do Über', u'Über Update'), wordFilter(u'Sistema de obtenção de itens', u'Sistema de drop de itens', u'Item drop system', u'Sistema de queda de itens', keepcapitalization=True), wordFilter(u'Rajada de ar', u'Compression blast', u'Airblast', keepcapitalization=True), wordFilter(u'minicrits', u'Mini-Crits', u'Mini-Críticos', u'Minicríticos', keepcapitalization=True), wordFilter(u'minicrit', u'Mini-Crit', u'Mini-Crítico', u'Minicrítico', keepcapitalization=True), wordFilter(u'Blog Oficial do TF2', u'TF2 Official Blog', u'Blog Oficial de TF2'), wordFilter(u'Personalizada', u'Customizada', keepcapitalization=True), wordFilter(u'Personalizado', u'Customizado', keepcapitalization=True), wordFilter(u'Compartimento', u'Slot', keepcapitalization=True), wordFilter(u'Jogabilidade', u'Gameplay', keepcapitalization=True), wordFilter(u'Rei do Pedaço', u'King of the Hill'), wordFilter(u'Disparo-alt', u'Disparo-Alt', u'Alt-Fire', keepcapitalization=True), wordFilter(u'Disparo alternativo', u'Fogo Alternativo', u'Disparo Alternativo', keepcapitalization=True), wordFilter(u'Botão de disparo-alt', u'Botão Direito do Mouse', keepcapitalization=True), wordFilter(u'Habilidades', u'Abilidades', keepcapitalization=True), wordFilter(u'Austrálio', u'Australium', keepcapitalization=True), wordFilter(u'Cavaleiro Carente de Cavalo e Cabeça', u'Horseless Headless Horsemann'), wordFilter(u'Festiva', u'Festive', keepcapitalization=True), wordFilter(u'Natal Australiano', u'Australian Christmas', keepcapitalization=True), wordFilter(u'Natal Australiano de 2011', u'Natal Australiano 2011', u'Australian Christmas 2011', keepcapitalization=True), wordFilter(u'== Variantes pintadas ==', u'==+ *Variantes pintados *==+', u'==+ *Variações pintadas *==+', u'==+ *Variantes pintáveis *==+'), wordFilter(u'== Curiosidades ==', u'==+ *Trivias? *==+'), wordFilter(u'Projeto', u'Planta', keepcapitalization=True), wordFilter(u'Projetos', u'Plantas', keepcapitalization=True), wordFilter(u'== Histórico de atualizações ==', u'==+ *Histórico de atualização? *==+', u'==+ *Update history? *==+'), wordFilter(u'pré-venda', u'pré-compra'), wordFilter(u'Dia das Bruxas', u'Halloween', keepcapitalization=True), wordFilter(u'Engine Source', u'Source Engine', u'Motor Source'), wordFilter(u'itens', u'items', keepcapitalization=True), wordFilter(u'pré-visualização', u'preview', u'previsão', keepcapitalization=True), wordFilter(u'pré-visualizações', u'previews', u'previsões', keepcapitalization=True), wordFilter(u'no Steam', u'na Steam+'), wordFilter(u'do Steam', u'da Steam+'), wordFilter(u'à Oficina Steam', u'para a Oficina Steam+'), wordFilter(u'cor da equipe', u'cor do time', u'cor de time', u'cor de equipe'), wordFilter(u'Minissentinelas', u'Minisentinelas', u'Mini-Sentinelas, keepcapitalization=True'), wordFilter(u'Minissentinela', u'Minisentinela', u'Mini-Sentinela, keepcapitalization=True'), wordFilter(u'Natal', u'Smissmas'), wordFilter(u'MvM', u'MVM'), wordFilter(u'Mann vs. Máquina', u'Mann vs Máquina', u'Mann vs. Machine', u'Mann vs Machine'), wordFilter(u'interface', u'HUD', keepcapitalization=True), wordFilter(u'captura de tela', u'screenshot', keepcapitalization=True), wordFilter(u'capturas de tela', u'screenshots', keepcapitalization=True), wordFilter(u'→', u'->', u'-->'), wordFilter(u'←', u'<-', u'<--'), wordFilter(u'conversa', u'bate-papo', u'bate papo', u'chat', keepcapitalization=True), wordFilter(u'on-line', u'online', keepcapitalization=True), wordFilter(u'off-line', u'offline', keepcapitalization=True), wordFilter(u'tiros fatais na cabeça', u'tiros na cabeça fatais', keepcapitalization=True), wordFilter(u'Loja Mann Co\.', u'Loja Mann Co\.\.', u'Loja da Mann Co\.'), wordFilter(u'Peça Estranha', u'parte estranha'), language='pt-br' )
addSafeFilter( # Quality names wordFilter(u'Genuíno', u'Genuine', keepcapitalization=True), wordFilter(u'Feito por Mim', u'Self-Made', keepcapitalization=True), wordFilter(u'Normal', u'Stock', keepcapitalization=True), wordFilter(u'Estranho', u'Strange', keepcapitalization=True), wordFilter(u'Único', u'Unique', keepcapitalization=True), wordFilter(u'Incomum', u'Unusual', keepcapitalization=True), language='pt-br' )
addSafeFilter( # Weapon names wordFilter(u'Espingarda', u'Scattergun'), wordFilter(u'Pistola', u'Pistol'), wordFilter(u'Taco', u'Bat'), wordFilter(u'Lança-Foguetes', u'Rocket Launcher'), wordFilter(u'Escopeta', u'Shotgun'), wordFilter(u'Pá', u'Shovel'), wordFilter(u'Lança-Chamas', u'Flame Thrower' u'Flamethrower'), wordFilter(u'Machado de Incêndio', u'Fire Axe'), wordFilter(u'Lança-Granadas', u'Grenade Launcher'), wordFilter(u'Lança-Stickybombs', u'Stickybomb Launcher'), wordFilter(u'Garrafa', u'Bottle'), wordFilter(u'Metralhadora Giratória', u'Minigun'), wordFilter(u'Punhos', u'Fists'), wordFilter(u'Ferramenta de Construção', u'Build PDA'), wordFilter(u'Ferramenta de Demolição', u'Destroy PDA'), wordFilter(u'Arma de Seringas', u'Syringe Gun'), wordFilter(u'Arma Médica', u'Medi Gun'), wordFilter(u'Serra de Ossos', u'Bone Saw'), wordFilter(u'Rifle de Precisão', u'Rifle de Sniper', u'Sniper Rifle', keepcapitalization=True), wordFilter(u'Submetralhadora', u'SMG'), wordFilter(u'Revólver', u'Revolver'), wordFilter(u'Faca', u'Knife'), wordFilter(u'Relógio de Invisibilidade', u'Invis Watch', u'Invisibility Watch'), wordFilter(u'Kit de Disfarce', u'Disguise Kit'), language='pt-br' )
enforceCapitalization( # Do not capitalize languages, nationalities, months or days of the week (will not include abbrevs. of weekdays, as they are also ordinal numbers) u'inglês', u'russo', u'russa', u'francês', u'francesa', u'alemão', u'alemã', u'polonês', u'polonesa', u'brasileiro', u'brasileira', u'finlandês', u'finlandesa', u'castelhano', u'espanhol', u'espanhola', u'holandês', u'holandesa', u'chinês', u'chinesa', u'chinês simplificado', u'chinês tradicional', u'tailandês', u'tailandesa', u'ucraniano', u'ucraniana', u'árabe', u'tcheco', u'tcheca', u'dinamarquês', u'dinamarquesa', u'húngaro', u'húngara', u'italiano', u'italiana', u'japonês', u'japonesa', u'coreano', u'coreana', u'norueguês', u'norueguesa', u'português', u'portuguesa', u'romeno', u'romena', u'sueco', u'sueca', u'turco', u'egípcio', u'egípcia', u'janeiro', u'fevereiro', u'março', u'abril', u'maio', u'junho', u'julho', u'agosto', u'setembro', u'outubro', u'novembro', u'dezembro', u'segunda-feira', u'terça-feira', u'quarta-feira', u'quinta-feira', u'sexta-feira', u'sábado', u'domingo', language='pt-br' )
Russian filters
addSafeFilter( regexes({ (u'^([^<>]*)(?<!=)([""])((?:(?!\\2|[=<>]).)+)\\2([^<>]*)$', re.MULTILINE): u'$1«$3»$4', u'«([^»]*)(?<!\')\'([^»\']+)\'(?!\')': u'«$1„$2“' }), language='ru' )
addSafeFilter( # Requested by FreeXMan wordFilter(u'Зефенайя', u'Зефанайя', u'Зефинайя'), wordFilter(u'Зефенайи', u'Зефанайи', u'Зефинайи'), wordFilter(u'Зефенайей', u'Зефанайей', u'Зефинайей'), wordFilter(u'Зефенай', u'Зефанай', u'Зефинай'), wordFilter(u'Нет', u'N/A'), wordFilter(u'Хеллоуин', u'Хэллоуин', keepcapitalization=True), wordFilter(u'Хеллоуинский', u'Хэллоуинский', keepcapitalization=True), wordFilter(u'Хеллоуинского', u'Хэллоуинского', keepcapitalization=True), wordFilter(u'Хеллоуинскому', u'Хэллоуинскому', keepcapitalization=True), wordFilter(u'Хеллоуинским', u'Хэллоуинским', keepcapitalization=True), wordFilter(u'Хеллоуинском', u'Хэллоуинском', keepcapitalization=True), language='ru' )
Korean filters
addSafeFilter(dumbReplace(u'솔져', u'솔저'), language='ko') # Requested by Cyrus H.
Dutch filters
#addSafeFilter(wordFilter(u'Niveau', u'Level', keepcapitalization=True), language='nl') # Requested by Apparition; too broad
addSafeFilter( # Requested by Warlike and Heifastus wordFilter(u'Updateverleden', u'Updategeschiedenis', u'Update verleden', u'Update geschiedenis', keepcapitalization=True), wordFilter(u'Galerij', u'Gallerij', keepcapitalization=True), wordFilter(u'community', u'communitie', keepcapitalization=True), language='nl' )
addSafeFilter( # Requested by Robin0van0der0vliet wordFilter(u'officieel', u'offici[eë][eë]l', keepcapitalization=True), wordFilter(u'officiële', u'offic[iï]ele', keepcapitalization=True), language='nl' )
addSafeFilter( # Requested by Eels wordFilter(u'voorwerpvindsysteem', u'voorwerp vind systeem', keepcapitalization=True), wordFilter(u'Voorwerpschema', u'Voorwerp schema', keepcapitalization=True), wordFilter(u'Voorwerpschemaupdate', u'Voorwerp schema-update', u'Voorwerpschema update', keepcapitalization=True), language='nl' )
addSafeFilter( # Requested by GrampaSwood regex(r'\b, en\b', ' en'), wordFilter(u'grondvoorwerpen', u'pickups', keepcapitalization=True), wordFilter(u'grondvoorwerp', u'pickup', keepcapitalization=True), wordFilter(u'bijgewerkt', u'geüpdatet', keepcapitalization=True), wordFilter(u'weetjes', u'trivia', keepcapitalization=True), language='nl' )
Polish filters
addSafeFilter( # Requested by real_alien wordFilter(u'Exploit', u'Nadużycie', keepcapitalization=True), wordFilter(u'Exploity', u'Nadużycia', keepcapitalization=True), wordFilter(u'Exploitem', u'Nadużyciem', keepcapitalization=True), wordFilter(u'Exploitu', u'Nadużycia', keepcapitalization=True), language='pl' )
Hungarian filters
addSafeFilter( # Requested by Monte wordFilter(u'== Frissítési előzmények ==', u'==+ ?*Frissítések ?==+', u'==+ ?*Update ?==+', u'==+ ?*Javítások ?==+'), wordFilter(u'== Lásd még ==', u'==+ ?See also ?==+'), wordFilter(u'== Források ==', u'==+ ?References ?==+'), wordFilter(u'== Kulisszák mögött ==', u'==+ ?Érdekességek ?==+', u'==+ ? Trivia ?==+'), wordFilter(u'== Festett variációk ==', u'==+ ?Painted variants ?==+'), wordFilter(u'== Sebzési és működési idők ==', u'==+ ?Damage and function times ?==+', u'==+ ?Sebzési? és [Ff]unkció idők ?==+'), wordFilter(u'=== Mint barkácsolási kellék ===', u'==+ ?As a crafting ingredient ?==+'), wordFilter(u'== Nem használt tartalom ==', u'==+ ?Unused content ?==+'), wordFilter(u'== Öszefüggő teljesítmények ==', u'==+ ?Related achievements ?==+', u'==+ ?[Aa]chievements ?==+', u'==+ ?Teljesítmények ?==+'), wordFilter(u'== Fura ritkaságú ==', u'==+ ?Strange variant ?==+'), wordFilter(u'== Tárgy-szett ==', u'==+ ?Item set ?==+', u'==+ ?Tárgyszett ?==+', u'==+ ?Tárgy szett ?==+'), language='hu' )
Swedish filters
addSafeFilter( # Requested by BrazilianNut wordFilter(u'Ingenjörer', u'Tekniker', keepcapitalization=True), wordFilter(u'Ingenjören', u'Teknikern', keepcapitalization=True), language='sv' )
Link filters
tf2.com to teamfortress.com
addLinkFilter(linkDomainFilter('tf2.com', 'teamfortress.com'))
Moved links
def movedLinks(link, **kwargs): movedLinks = { u'Quality#Normal': u'Normal', u'Quality#Unique': u'Unique', u'Quality#Vintage': u'Vintage', u'Quality#Genuine': u'Genuine', u'Quality#Strange': u'Strange', u'Quality#Unusual': u'Unusual', u'Quality#Community': u'Community (quality)', u'Quality#Self-Made': u'Self-Made', u'Quality#Valve': u'Valve (quality)', u'vdc:Material': u'vdc:VMT' } if link.getType() == u'internal' and link.getLink() in movedLinks: link.setLink(movedLinks[link.getLink()]) return link addLinkFilter(movedLinks)
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)
Fix external wiki links that should be internal links
def fixExternalToInternalLinks(link, **kwargs): wikiExternalRe = compileRegex(r'^https?://wiki\.(teamfortress|tf2)\.com/wiki/(\S+)$') if link.getType() == 'external': linkMatch = wikiExternalRe.search(link.getLink()) if linkMatch: link.setType('internal') try: wikiPage = u(urllib2.unquote(str(linkMatch.group(2))).decode('utf8', 'ignore').replace(u'_', ' ')) except: wikiPage = u(linkMatch.group(2)).replace(u'_', ' ') link.setLink(wikiPage) if link.getLabel() is None: link.setLabel(wikiPage) return link addLinkFilter(fixExternalToInternalLinks)
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)
Remove trailing slashes from internal links
def removeTrailingSlash(l, **kwargs): if l.getType() != u'internal' or not len(l.getLink()): return l if l.getLink()[-1] == '/': l.setLink(l.getLink()[:-1]) return l addLinkFilter(removeTrailingSlash)
Convert patch links to {{Patch name}}
def patchNameLinkFilter(l, **kwargs): if l.getType() != u'internal': return l regPatchName = compileRegex(u'(January|February|March|April|May|June|July|August|September|October|November|December)\\s+(\\d+),\\s+(\\d{4,})\\s+Patch(?:/\\w+)?') result = regPatchName.match(l.getLink()) if result is None or l.getLabel().find(result.group(2)) == -1 or l.getLabel().find(result.group(3)) == -1: return l monthNames = ('january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december') patchType = u if l.getLink().lower().find(u'beta') != -1: patchType = u'|beta' elif l.getLink().lower().find(u'xbox') != -1: patchType = u'|xbox' elif l.getLink().lower().find(u'classic') != -1: patchType = u'|classic' return template(u'{{Patch name|' + u(monthNames.index(result.group(1).lower()) + 1) + u'|' + u(result.group(2)) + u'|' + u(result.group(3)) + patchType + u'}}') addLinkFilter(patchNameLinkFilter)
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'], 'Class infobox': ['infobox class'], 'Video infobox': ['infobox video'], 'Comic infobox': ['infobox comics'] } for n in templateMap: if t.getName().lower() in templateMap[n]: t.setName(n) return t addTemplateFilter(templateRenameMapping)
Lang template renaming
def langsTemplateRenameMapping(t, **kwargs): templateMap = {'Scout Nav': 'scout nav', 'Soldier Nav': 'soldier nav', 'Pyro Nav': 'pyro nav', 'Demoman Nav': 'demoman nav', 'Heavy Nav': 'heavy nav', 'Engineer Nav': 'engineer nav', 'Medic Nav': 'medic nav', 'Sniper Nav': 'sniper nav', 'Spy Nav': 'spy nav', 'Audio Nav': 'audionav', 'Video Nav': 'videonav', 'Class Strategy Nav': 'class strategy' } for template in templateMap: langTemplateRe = compileRegex(templateMap[template] + '/(ar|cs|da|de|es|fi|fr|hu|it|ja|ko|nl|no|pl|pt|pt-br|ro|ru|sv|tr|zh-hans|zh-hant)') if langTemplateRe.match(t.getName().lower()): t.setName(template) return t addTemplateFilter(langsTemplateRenameMapping)
Reindent all infoboxes
def infoboxIndentFilter(t, **kwargs): itemInfoboxes = ('item infobox', 'map infobox', 'item set infobox', 'mission infobox', 'class infobox', 'hazard infobox', 'pickup infobox', 'video infobox', 'comic infobox', 'website infobox', 'company infobox') tName = t.getName().lower() if 'infobox' in tName and tName not in itemInfoboxes: t.indentationMatters(True) t.setDefaultIndentation(2) return t addTemplateFilter(infoboxIndentFilter, lowPriority=True)
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', 'loadout-prefix': 'hide-loadout-prefix' } catstoCheck = { # Mapping 'templateAttribute': [List of 'Category|templateAttributeValue'] 'type': ['Taunts|taunt', 'Action items|action', 'Taunts|action taunt', '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', '3d-team', '3d-alt', '3d-team-alt', '3d-image-#', '3d-button-#', '3d-viewname-#', 'number-of-3d-images', 'number-of-3d-team-images', 'number-of-3d-alt-images', 'view#', 'view#name', 'team-colors', 'team-colors-class#', 'team-colors-class#-name', 'team-colors-width', 'two-models', 'skin-image-red', 'skin-image-blu', 'tfc-model', 'tfc-model-3d-image-#', 'tfc-model-3d-viewname-#', 'tfc-model-3d-button-#', 'qtf-model', 'hide-kill-icon', 'kill-icon-#', 'kill-text-#', 'kill-tooltip-#', 'used-by', 'slot', 'crafting-slot', 'custom-slot', 'equip-region', 'equip-region-#', 'weapon-script', 'contributed-by', 'released', 'released-major', 'availability', 'trade', 'gift', 'marketable', 'craft', 'paint', 'rename', 'numbered', 'medieval', 'ammo-loaded', 'ammo-carried', 'ammo-type', 'show-ammo', 'reload', 'loadout', 'loadout-prefix', 'prefix', 'suffix', 'quality', '%ATTRIBUTES%', 'item-kind', 'item-level', 'level', 'paint-color', 'decal-icon', 'unusual-icon', 'stat-icon', 'strange-icon', 'pyroland-icon', 'halloween-icon', 'limited', 'unusual-effect', 'grade', 'wear', 'item-description', 'item-uses', 'item-flags', 'item-expiration' ] checkEnglish = ['trade', 'gift', 'marketable', 'craft', 'paint', 'rename', 'numbered', 'medieval', 'contributed-by', 'equip-region', 'equip-region-#'] # 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, delete pricing attributes t.delParam('ammo', 'price', 'show-price', 'purchasable', 'backpack-image') # Step 3 - Fix reload stuff if t.getParam('reload') is None: t.renameParam('reload-type', 'reload') # Step 4 - 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 5 - 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(u'', kwargs['article'].title)) try: englishContent = englishArticle.getWikiText() except: englishContent = u englishContent, englishTemplates, englishKeys = templateExtract(englishContent) for englishT in englishTemplates.values(): 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: values[attr] = templateRestore(englishT.getParam(attr), englishTemplates, englishKeys) 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 6 - 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: pass#t.setParam(cname, found) # Temporarily disabled for Made Man if u'Category:Weapons' not in tCats: t.delParam('slot') # Step 7 - 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))""" # Step 7.5 TEMPORARY: Add numbered = no if not isTFC and not t.getParam('numbered'): t.setParam('numbered', 'no') # Step 8 - Do TFC stuff if isTFC: t.setParam('type', 'weapon') t.setParam('game', 'tfc') # Step 9 - Set correct preferred indentation for k in ['quality', 'item-level', 'level', 'item-description', 'item-uses', 'item-flags', 'level-and-type', 'loadout-name', 'loadout-prefix', 'hide-loadout-prefix', 'limited', 'grade', 'wear', 'unusual-effect', 'item-kind', 'prefix', 'suffix', 'decal-icon', 'unusual-icon', 'stat-icon', 'strange-icon', 'pyroland-icon', 'halloween-icon', 'custom-icon', 'killcount', 'rankson', 'rankson2', 'rankson3', 'rankson4', 'rankson5', 'rankson6', 'rankson7', 'rankson8', 'rankson9', 'item-expiration']: t.setPreferedIndentation(k, 2) for i in range(1, 10): for a in attributeTypes: t.setPreferedIndentation('att-' + str(i) + '-' + a, 2) # Step 10 - 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, 30): newOrder.append(o.replace('#', str(i))) t.setPreferedOrder(newOrder) # Step 11 - replace deprecated hat/misc types if t.getParam('type') is not None and t.getParam('type').lower() in (u'hat', u'hats', u'head', u'headwear', u'misc item', u'misc.', u'misc', u'miscellaneous', u'miscellaneous item'): t.setParam('type', 'cosmetic') # 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', u'auto lang cat'): return None # Delete template return t addTemplateFilter(removeUselessTemplate)
Replace Wikipedia link template with interwiki link
def replaceWikipediaTemplate(t, **kwargs): if 'article' not in kwargs: return t if t.getName().lower() != 'w': return t link = u'[[w:' if t.getParam('lang'): link += t.getParam('lang') + u':' link += t.getParam('1') + u'|' if t.getParam('2'): link += t.getParam('2') else: link += t.getParam('1') link += u']]' return link addTemplateFilter(replaceWikipediaTemplate, lowPriority=True)
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'], 'update history': [1], 'otherwikis': [1, 2] } 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', 'result-#n-local'], 'taunt': ['weapon-#n-local'], 'patch layout': ['diff-#n'], 'item infobox': ['crafting-slot'], 'model info': ['location'], 'map infobox': ['map-stamp-link'] } 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, lowPriority=True)
Add day
/month
/year
to {{Patch layout}}
def patchLayoutFilter(t, **kwargs): if t.getName().lower() != 'patch layout' or 'article' not in kwargs: return t t.setPreferedOrder(['game', 'before', 'day', 'month', 'year', 'after', 'source-title', 'source', 'source-lang'] + [['source-' + str(n) + '-title', 'source-' + str(n), 'source-' + str(n) + '-lang'] for n in xrange(10)] + ['updatelink', 'update', 'update-link', 'update-lang', 'hide-diff'] + [['diff-' + str(n)] for n in xrange(10)] + ['notes']) t.delParam('current') regPatchName = compileRegex(u'^(January|February|March|April|May|June|July|August|September|October|November|December)\\s+(\\d+),\\s+(\\d{4,})\\s+Patch(?:/\\w+)?') result = regPatchName.match(u(kwargs['article'].title)) if result is not None: t.setParam('day', result.group(2)) t.setParam('month', result.group(1).lower()) t.setParam('year', result.group(3)) return t addTemplateFilter(patchLayoutFilter)
Implement {{Dictionary}}
class DictionaryUpdater: def __init__(self): self.subpageTemplateLang = """{{#switch:{{{lang|{{SUBPAGENAME}}}}}|%options%}}""" self.subpageTemplateParam = """{{#switch:{{{1|}}}|%options%}}""" self.invalidParamError = """<span class="error">Error: invalid param.</span>[[Category:ERROR]]""" self.invalidKeyNameCharacters = """#<>[]{}""" self.subpageTemplateID = """%string%""" self.partialUpdateThreshold = 750 # Update SyncData every n edits 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', 'allTemplate': '{{{{{template|item price/fmt}}}|%options%|tt={{{tt|yes}}}}}' }, u'Template:Dictionary/merchandise': { 'name': 'merchandise', 'sync': 'Template:Dictionary/merchandise/Special:SyncData' }, u'Template:Dictionary/decorated': { 'name': 'decorated', 'sync': 'Template:Dictionary/decorated/Special:SyncData' }, u'Template:Dictionary/descriptions': { 'name': 'descriptions', 'sync': 'Template:Dictionary/descriptions/Special:SyncData' }, u'Template:Dictionary/dyk': { 'name': 'dyk', 'sync': 'Template:Dictionary/dyk/Special:SyncData' }, u'Template:Dictionary/tournament medals': { 'name': 'tournament medals', 'sync': 'Template:Dictionary/tournament medals/Special:SyncData' }, u'Template:Dictionary/attributes': { 'name': 'attributes', 'sync': 'Template:Dictionary/attributes/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' }, u'Template:Dictionary/achievements/summer camp': { 'name': 'achievements/summer camp', 'sync': 'Template:Dictionary/achievements/summer camp/Special:SyncData' }, u'Template:Dictionary/achievements/foundry': { 'name': 'achievements/foundry', 'sync': 'Template:Dictionary/achievements/foundry/Special:SyncData' }, u'Template:Dictionary/achievements/christmas': { 'name': 'achievements/christmas', 'sync': 'Template:Dictionary/achievements/christmas/Special:SyncData' }, u'Template:Dictionary/achievements/astro-chievements': { 'name': 'achievements/astro-chievements', 'sync': 'Template:Dictionary/achievements/astro-chievements/Special:SyncData' }, u'Template:Dictionary/achievements/mann vs. machievements': { 'name': 'achievements/mann vs. machievements', 'sync': 'Template:Dictionary/achievements/mann vs. machievements/Special:SyncData' }, u'Template:Dictionary/achievements/standin': { 'name': 'achievements/standin', 'sync': 'Template:Dictionary/achievements/standin/Special:SyncData' }, u'Template:Dictionary/achievements/process': { 'name': 'achievements/process', 'sync': 'Template:Dictionary/achievements/process/Special:SyncData' }, u'Template:Dictionary/achievements/snakewater': { 'name': 'achievements/snakewater', 'sync': 'Template:Dictionary/achievements/snakewater/Special:SyncData' }, u'Template:Dictionary/achievements/powerhouse': { 'name': 'achievements/powerhouse', 'sync': 'Template:Dictionary/achievements/powerhouse/Special:SyncData' }, u'Template:Dictionary/achievements/other games': { 'name': 'achievements/other games', 'sync': 'Template:Dictionary/achievements/other games/Special:SyncData' }, u'Template:Dictionary/achievements/pass time': { 'name': 'achievements/pass time', 'sync': 'Template:Dictionary/achievements/pass time/Special:SyncData' }, u'Template:Dictionary/blueprints': { 'name': 'blueprints', 'sync': 'Template:Dictionary/blueprints/Special:SyncData' }, u'Template:Dictionary/defindex': { 'name': 'defindex', 'sync': 'Template:Dictionary/defindex/Special:SyncData' }, u'Template:Dictionary/quad': { 'name': 'quad', 'sync': 'Template:Dictionary/quad/Special:SyncData', 'blankString': '-' }, u'Template:Dictionary/gameinfo': { 'name': 'gameinfo', 'sync': 'Template:Dictionary/gameinfo/Special:SyncData' } } self.subpageSeparator = u'/' # List of supported languages, in prefered order self.languages = [u'en', u'ar', u'bg', 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'th', u'tr', u'uk', u'vi', u'zh-hans', u'zh-hant'] self.defaultLang = u'en' self.allKeyName = u'_all_' self.filterName = u'Your friendly neighborhood dictionary updater' self.commentsExtract = compileRegex(r) self.subkeyName = compileRegex(r'^([-\w]+)$', re.IGNORECASE) addWhitelistPage(self.dictionaries.keys()) self.editCounts = {} def updateSyncData(self, currentDict, syncData, note=): # Build syncdata string representation syncKeys = syncData.keys() syncKeys.sort() syncLines = [] for k in syncKeys: syncLines.append(k + u':' + syncData[k]) if note: note = u' (' + u(note) + u')' editPage(self.dictionaries[currentDict]['sync'], u'\n'.join(syncLines), summary=u'Updated synchronization information for [[:' + currentDict + u']]' + note + u'.', minor=True, nocreate=False) 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 'blankString' in self.dictionaries[currentDict] and data[k] == self.dictionaries[currentDict]['blankString']: data[k] = u if isTranslation and k not in self.languages: isTranslation = False subpage = u(self.subpageTemplateParam) ordered = [] unordered = {} if isTranslation: missing = [] for lang in self.languages: if lang in data: ordered.append(lang + u'=' + data[lang]) unordered[lang] = 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.insert(0, 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]) unordered[k] = data[k] h.update((k + u'=' + data[k]).encode('utf8')) if 'allTemplate' in self.dictionaries[currentDict] and (len(unordered) or len(self.dictionaries[currentDict]['allTemplate']['params'])): allKey = [] keys = unordered.keys() keys.sort() for k in keys: allKey.append(k + u'=' + unordered[k]) insertIndex = 0 if isTranslation and self.defaultLang in data: insertIndex = 1 ordered.insert(insertIndex, u(self.allKeyName) + u'=' + u(self.dictionaries[currentDict]['allTemplate'].replace(u'%options%', u'|'.join(allKey)))) 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 subpage = subpage.replace(u'%dictionary%', currentDict) subpage = subpage.replace(u'%dictionaryname%', self.dictionaries[currentDict]['name']) subpage = subpage.replace(u'%keyname%', keyName) if editPage(currentDict + self.subpageSeparator + keyName, subpage, summary=u'Pushed changes from [[:' + currentDict + u']] for string "' + keyName + u'".', minor=True, nocreate=False): syncData[keyName] = h # Update sync data if currentDict not in self.editCounts: self.editCounts[currentDict] = 0 self.editCounts[currentDict] += 1 if self.editCounts[currentDict] > self.partialUpdateThreshold: self.editCounts[currentDict] = 0 self.updateSyncData(currentDict, syncData, 'Partial update') def dedup(self, l): s = set() for i in l: if i not in s: s.add(i) yield i def processComment(self, commentString, currentDict, definedStrings, syncData): commentContents = [] commentString = u(commentString).replace(u'\r', u) parseState = { 'currentKeys': [], 'currentSubkeys': {}, 'currentKeyIsValid': False, 'abortCurrentSubkeys': False, 'subKeyLines': {}, } subPageData = {} def finalize(): if parseState['currentKeyIsValid']: # End processing of current set of subkeys. if not parseState['abortCurrentSubkeys']: isTranslation = True for k in parseState['currentKeys']: assert k not in subPageData, 'Internal logic consistency error: duplicate key %r' % (k,) subPageData[k] = {} for subKey, data in parseState['currentSubkeys'].items(): isTranslation = isTranslation and subKey in self.languages subPageData[k][subKey] = data if isTranslation: for lang in self.languages: if lang in parseState['subKeyLines']: commentContents.append(parseState['subKeyLines'][lang]) else: for subKey in sorted(parseState['subKeyLines'].keys()): commentContents.append(parseState['subKeyLines'][subKey]) parseState['abortCurrentSubkeys'] = False parseState['currentKeyIsValid'] = False for line in commentString.split(u'\n'): if u'WINDBOT_INVALID' in line: commentContents.append(line) continue def badLine(reason): if parseState['abortCurrentSubkeys']: for k in sorted(parseState['subKeyLines'].keys()): commentContents.append(parseState['subKeyLines'][k] + u' // WINDBOT_INVALID Other subkeys for this key are invalid') parseState['subKeyLines'] = {} if parseState['currentKeyIsValid'] and not parseState['abortCurrentSubkeys']: parseState['abortCurrentSubkeys'] = True else: parseState['currentKeyIsValid'] = False commentContents.append(line + u' // WINDBOT_INVALID ' + u(reason.replace(u':', ' '))) if line.strip() == u: finalize() commentContents.append(line) continue if line.strip()[0] == u'#': # Human comment commentContents.append(line) continue if line[0] not in (u' ', '\t'): # Key, or key + no-subkey data if line.find(u':') == -1: # Colon was probably forgotten badLine('Maybe a forgotten colon?') continue if parseState['currentKeyIsValid']: badLine('Missing linebreak before new key?') continue beforeColon, afterColon = line.split(u':', 1) afterColon = afterColon.strip() # Check keys. keyNames = [k.replace(u'_', u' ').replace(u'#', u).strip().lower() for k in beforeColon.strip().split(u'|')] keyNames = [k for k in self.dedup(keyNames) if k] if len(keyNames) == 0: badLine('No valid key names') continue duplicateKey = None badKey = None for k in keyNames: for c in self.invalidKeyNameCharacters: if c in k: badKey = k break if k in definedStrings: duplicateKey = k break if k in subPageData: duplicateKey = k break if duplicateKey: badLine('Duplicate key: %r' % duplicateKey) continue if badKey: badLine('Key has invalid characters: %r' % badKey) continue # Key looks good. for k in keyNames: definedStrings.add(k) # Check for no-subkey data. if afterColon: for k in keyNames: subPageData[k] = afterColon commentContents.append(u' | '.join(keyNames) + u': ' + afterColon) else: parseState['currentKeyIsValid'] = True parseState['abortCurrentSubkeys'] = False parseState['currentKeys'] = keyNames parseState['currentSubkeys'] = {} parseState['subKeyLines'] = {} commentContents.append(u' | '.join(keyNames) + u':') continue # Sub-key definition follows (has a space as first character). if parseState['abortCurrentSubkeys']: commentContents.append(line) continue if not parseState['currentKeyIsValid']: badLine('Sub-key being defined despite no valid key') continue if line.find(u':') == -1: # Colon was probably forgotten badLine('Missing colon in subkey definition') continue beforeColon, afterColon = line.strip().split(u':', 1) subKeyNames = [k.strip().lower() for k in beforeColon.strip().split(u'|')] subKeyNames = [k for k in self.dedup(subKeyNames) if k] badSubkeyName = None duplicateSubkey = None for k in subKeyNames: if not self.subkeyName.match(k): badSubkeyName = k break if k in parseState['currentSubkeys'].keys(): duplicateSubkey = k break if badSubkeyName: badLine('Invalid subkey name: %r' % badSubkeyName) continue if duplicateSubkey: badLine('Duplicate subkey name: %r' % duplicateSubkey) continue subKeyData = afterColon.strip() if not subKeyData: badLine('Empty data') continue for k in subKeyNames: parseState['currentSubkeys'][k] = subKeyData parseState['subKeyLines'][k] = u' ' + k + u': ' + subKeyData finalize() for k, data in subPageData.items(): self.generateSubpage(k, data, currentDict, syncData) return u'\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) if random.randint(0, 50) == 0: # With probability 2%, ignore syncdata completely. Helps with stale syncdata and people overwriting things. syncDataText = u else: try: syncDataText = u(page(self.dictionaries[currentDict]['sync']).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':', 1) if len(sync) == 2: syncData[sync[0]] = sync[1] oldSyncData = syncData.copy() newContent = u previousIndex = 0 definedStrings = set() for comment in self.commentsExtract.finditer(content): newContent += content[previousIndex:comment.start()] previousIndex = comment.end() # Process current comment newContent += u'<!--\n\n' + self.processComment(u(comment.group(1)).strip(), currentDict, definedStrings, syncData) + u'\n\n-->' newContent += content[previousIndex:] # 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] self.updateSyncData(currentDict, syncData, 'Full update') self.editCounts[currentDict] = 0 return newContent def scheduledRun(self): for d in self.dictionaries: fixPage(d) dictUpdater = DictionaryUpdater() addFilter(dictUpdater) scheduleTask(dictUpdater.scheduledRun, 3)
Remove some templates on translated pages if they are absent on english page
def autoRemoveRecentAdditionTag(t, **kwargs): # "stub" is included here because translators often copy this template from the English page # instead of replacing it with "trans" or "update trans". # This wiki is English first, and as such, stub usage in translated pages is incorrect. # In the future, it might be more useful to just replace "stub" with "trans" or "update trans" instead. # - T 01/08/24 toRemoveTemplates = ('recent addition', 'ra', 'new item', 'unreleased', 'beta', 'stub') equivalences = { 'ra': 'recent addition', 'new item': 'recent addition' } if t.getName().lower() not in toRemoveTemplates or 'article' not in kwargs or '/' not in kwargs['article'].title: return t targetTemplate = t.getName().lower() if targetTemplate in equivalences: targetTemplate = equivalences[targetTemplate] englishArticle = page(kwargs['article'].title[:kwargs['article'].title.find('/')]) try: englishContent = englishArticle.getWikiText() englishContent, englishTemplates, englishKeys = templateExtract(englishContent) for t2 in englishTemplates.values(): if t2.getName().lower() in toRemoveTemplates: foundTemplate = t2.getName().lower() if foundTemplate in equivalences: foundTemplate = equivalences[foundTemplate] if foundTemplate == targetTemplate: return t return None except: return t addTemplateFilter(autoRemoveRecentAdditionTag)
File filters
Crush all PNG/JPG images
Delegated to CrushBOT.
Additional tasks
Update {{Dictionary/defindex}}
and {{Dictionary/price}}
TEMPORARILY DISABLED: WindBOT can't seem to properly fetch the item schema (probably an issue in steamGetGameSchema()), which causes this function to fail.
PhoneWave is taking care of this for now. See User:PhoneWave/forceDictionary/defindex.
def updateGameDictionaries(): if steam is None: return # Begin configurable section game = 440 priceDictionaryPage = u'Template:Dictionary/price' indexDictionaryPage = u'Template:Dictionary/defindex' itemEquivalences = { u'taunt: the (.*)': u'\\1', u'taunt: (.*)': u'\\1', u'taunt: the (.*)': u'\\1 (taunt)', # Useful for Meet the Medic u'taunt: (.*)': u'\\1 (taunt)', # Useful for Meet the Medic u'strange filter: (.*)': u'strange filter - \\1' } forceDefindex = { u'champ stamp': 815, u'confidential collection case': 5823, u'fall 2013 acorns crate key': 5710, u'flying guillotine': 812, u'gargoyle key': 5827, u'gloves of running urgently': 239, u'human cannonball': 817, u'huo-long heater': 811, u'lugermorph': 160, u'mann co. stockpile crate': 5738, u'mann co. stockpile crate key': 5740, u'mann co. store package': 729, u'mann co. strongbox key': 5720, u'mann co. supply crate key': 5021, u'marxman': 816, u'neon annihilator': 813, u'pda': 737, u'pyrovision goggles': 743, u'quarantined collection case': 5822, u'red-tape recorder': 810, u'something special for someone special': 5074, u'triad trinket': 814, u'what\'s in the team fortress 2 soundtrack box?': 1176, u'love and war cosmetics bundle': 2131, } decimalSeparator = '{{dec}}' illegalItemCharacters = compileRegex(r'[:%#\r\n\t]+') priceDictionaryHeader = u'{{dictionary/header}}\n== /price ==\n\'\'\'This dictionary is automatically updated by WindBOT\'\'\'. Edits made to this page will be overwritten.\n<!--\n' indexDictionaryHeader = u'{{dictionary/header}}\n== /defindex ==\n\'\'\'This dictionary is automatically updated by WindBOT\'\'\'. Edits made to this page will be overwritten.\n<!--\n' dictionaryFooter = u'\n-->' # End configurable section schema = steamGetGameSchema(game) assets = steamGetGameAssets(game) dictionary = {} defindex = {} for item in schema: itemName = u(item.name).lower().replace(u'_', u' ') keys = [itemName] for r in itemEquivalences: itemName = re.sub(u'^' + r + u'$', itemEquivalences[r], itemName) if itemName not in keys: keys.append(itemName) itemKeys = u' | '.join([illegalItemCharacters.sub(u'', x) for x in keys]) defindex[itemName] = itemKeys + u': ' + u(item.schema_id) if item not in assets: continue prices = assets[item.schema_id].price if not len(prices): continue prices = dict((u(k).strip().lower(), v) for k, v in prices.iteritems()) # Sanitize data dictionary[itemName] = itemKeys + u':' for currency in sorted(prices): price = prices[currency] if price.is_integer(): price = u(int(price)) else: price = u(round(price, 2)) dot = price.find(u'.') if dot == len(price) - 2: price += u'0' dictionary[itemName] += u'\n ' + u(currency).lower() + u': ' + price.replace('.', decimalSeparator) for item in forceDefindex: defindex[u(item)] = illegalItemCharacters.sub(u'', u(item)) + u': ' + u(forceDefindex[item]) if len(dictionary): # Is updating time doktor finalPage = priceDictionaryHeader for item in sorted(dictionary): finalPage += u'\n' + dictionary[item] + u'\n' finalPage += dictionaryFooter priceDictionaryPage = page(priceDictionaryPage) editPage(priceDictionaryPage, finalPage, summary=u'Updated item prices from WebAPI.', minor=True, bot=True, nocreate=True) # Run other filters on the new page fixPage(priceDictionaryPage) if len(defindex): # Is also updating time doktor finalPage = indexDictionaryHeader for item in sorted(defindex): finalPage += u'\n' + defindex[item] + u'\n' finalPage += dictionaryFooter indexDictionaryPage = page(indexDictionaryPage) editPage(indexDictionaryPage, finalPage, summary=u'Updated item indexes from WebAPI.', minor=True, bot=True, nocreate=True) # Run other filters on the new page fixPage(indexDictionaryPage) # scheduleTask(updateGameDictionaries, 2)
Update Lastpatch and Lastpatchbeta
# Redirect Updater Class - modified from http://github.com/i-ghost/wikiscripts # To update the lastpatch redirects on Team Fortress Wiki, but could probably be deployed on The Portal Wiki/Dota 2 Wiki # i-ghost class redirectUpdater(object): def __init__(self, updateTemplateName="Template:Updates", pageName="Lastpatch", betaPageName="Lastpatchbeta"): self.pageName = pageName self.betaPageName = betaPageName self.updateTemplateName = updateTemplateName self.pageText = page(self.pageName).getWikiText() self.betaPageText = page(self.betaPageName).getWikiText() self.updateTemplateText = page(self.updateTemplateName).getWikiText().split("\n") self.langs = ["ru", "fr", "de", "pl", "pt-br", "fi", "es", "nl", "zh-hans", "zh-hant", "ar", "cs", "da", "hu", "it", "ja", "ko", "no", "pt", "ro", "sv", "tr"] self._get_dates() self.patch = "%s %s, %s" % (self._get_month(int(self.updates["patch-month"])), self.updates["patch-day"], self.updates["patch-year"]) self.betaPatch = "%s %s, %s" % (self._get_month(int(self.updates["patch-beta-month"])), self.updates["patch-beta-day"], self.updates["patch-beta-year"]) self.footer = "<!-- This page is automatically generated when %s is modifed. Do not modify this page. -->" % (self.updateTemplateName) def _get_month(self, month): """WindBOT doesn't import calendar, so we use this instead""" return datetime.date(1960, month, 1).strftime("%B") def _get_dates(self): """Internal: Gets dates from update template and stores in dictionary""" self.updates = {} for i in self.updateTemplateText: if i.find("|") == 0: # Liable to break self.updates[i.lstrip("|").replace(" ", "").partition("=")[0]] = i.replace(" ", "").partition("=")[2].replace("<!--Don'tforgettoupdateme!-->", "") def make_edit_strings(self, beta=False, lang=False): """Creates the final page content.""" if beta and lang: self.redirect_string = "#REDIRECT [[%s Patch (Beta)/%s]] {{R lang|%s}}" % (self.betaPatch, lang, lang) self.summary_string = "Updated [[%s/%s]] redirect to [[%s Patch (Beta)/%s]]" % (self.betaPageName, lang, self.betaPatch, lang) elif beta and not lang: self.redirect_string = "#REDIRECT [[%s Patch (Beta)]]" % (self.betaPatch) self.summary_string = "Updated [[%s]] redirect to [[%s Patch (Beta)]]" % (self.betaPageName, self.betaPatch) if lang and not beta: self.redirect_string = "#REDIRECT [[%s Patch/%s]] {{R lang|%s}}" % (self.patch, lang, lang) self.summary_string = "Updated [[%s/%s]] redirect to [[%s Patch/%s]]" % (self.pageName, lang, self.patch, lang) elif not lang and not beta: self.redirect_string = "#REDIRECT [[%s Patch]]" % (self.patch) self.summary_string = "Updated [[%s]] redirect to [[%s Patch]]" % (self.pageName, self.patch) def _update_redirect(self, beta=False, lang=False): """Internal: Provides editing functionality""" self.make_edit_strings(beta, lang) # Use the correct page if beta: if lang: _pagetoedit = "%s/%s" % (self.betaPageName, lang) else: _pagetoedit = self.betaPageName else: if lang: _pagetoedit = "%s/%s" % (self.pageName, lang) else: _pagetoedit = self.pageName # Send the edit editPage(_pagetoedit, "%s\n%s" % (self.redirect_string, self.footer), summary=self.summary_string, minor=True, nocreate=False) def check_if_update_needed(self, beta=False): """Checks if redirects needs updating""" try: if beta: if self.betaPageText.split("[[")[1].partition("]]")[0].rstrip("Patch (Beta)") != self.betaPatch: return True else: if self.pageText.split("[[")[1].partition("]]")[0].rstrip(" Patch") != self.patch: return True except IndexError: return True # Just update anyway def update(self, beta=False): """Updates the redirects and their lang pages""" if beta: self._update_redirect(beta=True) for lang in self.langs: self._update_redirect(beta=True, lang=lang) else: self._update_redirect() for lang in self.langs: self._update_redirect(lang=lang) def run(self): """Runs everything""" # Beta if self.check_if_update_needed(beta=True): self.update(beta=True) # Normal if self.check_if_update_needed(): self.update() scheduleTask(redirectUpdater().run, 2)
Update game prices
Delegated to PhoneWave. See User:PhoneWave/forceDictionary/gameinfo for gameids.
Update {{Dictionary/steam ids}}
def updatePlayerInfoDictionary(): # By Smashman """A WindBOT filter created to fetch Steam user's name and vanity information and store it in Template:Dictionary/steam_ids""" # Begin configurable section playerInfoDictionaryHeader = u'{{dictionary/header}}\n== /steam_ids ==\n\'\'\'The information in this dictionary is automatically updated by WindBOT. Add new Steam IDs in [[Template:Dictionary/steam ids/id list]]\'\'\'.\n<!--\n' playerListDictionaryHeader = u'{{dictionary/header}}\n== /steam_ids/id_list ==\n\'\'\'The information in this dictionary is tracked by WindBOT. Add new Steam IDs to the bottom of this list\'\'\'.\n\n' playerInfoDictionaryFooter = u'\n-->' # End configurable section # Is updating time doktor playerInfoDictionaryPage = u'Template:Dictionary/steam ids' playerListDictionaryPage = u'Template:Dictionary/steam ids/id list' id_list = page('Template:Dictionary/steam ids/id list').getWikiText() id64s=sorted(frozenset(filter(lambda x: x.isdigit(), id_list.splitlines()))) listPage = playerListDictionaryHeader + '\n'.join(map(str, id64s)) + '\n' editPage(playerListDictionaryPage, listPage, summary=u'Sort tracked Steam IDs.', minor=True, bot=True, nocreate=True) start_limit = 0 end_limit = 100 i=0 div_100 = (len(id64s)/100)+1 #Times to repeat sections finalPage = playerInfoDictionaryHeader while i != div_100: id64_sort = sorted(id64s[start_limit:end_limit]) #Numerically sort the ids, for use in call id64s_csv = ','.join(id64_sort) #Put them into a csv string data = steam.api.interface("ISteamUser").GetPlayerSummaries(version = 2, steamids = id64s_csv) #Do the call data = sorted(data["response"]["players"], key=lambda x: x['steamid']) #Numerically sort by id for player in data: purl = player["profileurl"].strip('/') finalPage += u"\n{0}:".format(player["steamid"]) finalPage += u("\n nickname: <no" + u"wiki>{0}</no" + u"wiki>").format(player["personaname"].replace(u'<!--', u'<!--').replace(u'-->', u'-->')) if purl.find("/id/") != -1: vanity = os.path.basename(purl) finalPage += u"\n vanity: {0}".format(vanity) finalPage += u"\n" start_limit += 100 end_limit += 100 i+=1 finalPage += playerInfoDictionaryFooter editPage(playerInfoDictionaryPage, finalPage, summary=u'Updated steam player information from WebAPI.', minor=True, bot=True, nocreate=True) fixPage(playerInfoDictionaryPage) scheduleTask(updatePlayerInfoDictionary, 16) def updateDictionaryOnSteamIdListUpdate(content, **kwargs): if 'article' in kwargs and kwargs['article'] == 'Template:Dictionary/steam ids/id list': updatePlayerInfoDictionary() return content addFilter(updateDictionaryOnSteamIdListUpdate)