Szerkesztő:BinBot/portalsablon.py

Portálsablonok terítésére való bot. Ebben a formában a filmművészetportál sablonját teríti a film infoboxot tartalmazó lapokra, de a portál nevét és a lapgenerátort átírva könnyen alkalmazható más feladatra is. A megbeszéléseket egyelőre lásd lent, amíg nem archiválódnak.

A bot a Wikipédia:Szócikkek felépítése alapján – ha még nincs portálsablon – ezeket keresi a cikk utolsó szakaszában:

  1. Testvérprojektek sablonjai: {{commons, {{wiki (de nem {{wikia), {{társproj, {{testvérproj (lehet nagybetűvel is, és folytatódhat bárhogy – nincs megbízható felsorolás, elnevezési minta vagy kategória, amelynek alapján tökéletesen felismerhető lenne az összes)
  2. Csonksablonok: {{csonk}} vagy {{csonk-valami}} (megtalálja a csonk-dátumosakat is)
  3. Kategóriák: {{DEFAULTSORT:, [[Kategória: vagy [[Category: (lehet kisbetűs is)

Ha bármelyiket megtalálja, akkor elépakolja a portált. Ha többet is, akkor a legelső elé. Ha egyiket sem, akkor a cikk végére. (Interwikikkel 2017-ben már nem foglalkoztam, ne legyenek szócikkben.) Ha ezek közül valamelyiket eleve nem a javasolt felépítés szerinti helyen találja, arról nem a bot tehet, és nem is tud ezen segíteni. Mivel a WP:FELÉP egy gépi elemzésre alkalmatlan szerkezetet javasol, ennél pontosabb algoritmust nehéz lenne észszerű munkával és mérhető haszonnal kitalálni.

Meglévő portálsablonba (bármelyik szakaszban), ha még van benne hely, új argumentumként írja bele a portált, ha már megtelt, akkor rögtön utána új sablonba; ez esetben nem vizsgálja a meglévő sablon helyét. Ha a sablon elemzése során hibát talál, akkor megjegyzést ír a vitalapra, és nem illeszti be az új portált a sablonba.

ForráskódSzerkesztés

# -*- coding: utf-8 -*-
"""
Distributes portal template over several pages.
Written for Hungarian Wikipedia. Compat version.
Wikipédia:Botgazdák üzenőfala#Portálsablonok

TODO: making the generator more flexible, accepting the portal as parameter.
TODO: implementing multisession processing (continue where we stopped).
TODO: recognizing redirecting portals in the template.
TODO: search for a second template instead of creating new if the first one
      is full (not really likely case).
"""
# (C)(D)(E)(F)(G)(A)(H)(C) Bináris, 2017

import sys, re
import wikipedia as pywikibot
import pagegenerators
from binbotutils import munkanaplo # Private

site = pywikibot.getSite()
stat = munkanaplo.emptystat() # Private
# All the lines that begin with 'stat' are for my administratve use only.

class PortalBot:
    """
    This bot takes a pagegenerator, goes through the yielded pages,
    determines if the portal template with the given portal exist in it,
    and places the template or the new argument if neccessary.

    Parameters:
    gen - a pagegenerator that yields content pages (namespace=0)
    portal - the required portal name (unicode, without namespace prefix)
    ending - portal ending, see https://hu.wikipedia.org/wiki/Sablon:Port%C3%A1l
    """
    def __init__(self, gen, portal, ending):
        self.gen = gen
        self.portal = portal
        self.ending = ending
        self.portalWithEnding = portal
        if ending:
            self.portalWithEnding += '|' + ending
        self.portalRegex = regex(portal) # For searching this very portal
        self.generalPortalRegex = re.compile(ur'(?is)\{\{portál\s*\|(.*?)\}\}')
        sisterProjectTemplates = re.compile(
            ur'(?i)\{\{(commons|wiki(?!a)|t(árs|estvér)proj).*?\}\}')
        stubTemplates = re.compile(
            ur'(?i)\{\{csonk(-.+?)?\}\}')
        categories = re.compile(
            ur'(?i)(\{\{DEFAULTSORT|\[\[(Kategória|Category)):')
        self.followers = [sisterProjectTemplates, stubTemplates, categories]

    def run(self):
        sumbase = u'[[user:BinBot/portalsablon.py|' \
            u'Portálsablonok automatikus terítése bottal.]] '
        counter = 0 # Avoid black screen where nothing happens for a long time.
        for page in self.gen:
            counter += 1
            if not (counter % 20):
                print 'Counter=', counter
            # An ugly workaround for continuing after a break:
            # if counter < 2940:
                # continue
            try:
                text = page.get()
            except pywikibot.NoPage:
                pywikibot.output(page.title() + " doesn't exist.")
                stat['err'] += 1
                continue
            if self.portalRegex.search(text):
                stat['skip'] += 1
                # pywikibot.output(u'Rendben van: ' + page.title())
                continue

            try:
                if re.search(ur'(?i)\{\{\s*portál\s*\|',text):
                    pywikibot.output(u'Van portálsablon: ' + page.title())
                    newtext = self.addPortalToExisting(text)
                    summary = sumbase + \
                        u' A cikkben már volt portálsablon, új argumentum kell.'
                else:
                    pywikibot.output(u'Nincs portálsablon: ' + page.title())
                    newtext = self.addNewPortalTemplate(page, text)
                    summary = sumbase + \
                        u' A cikkben még nem volt portálsablon.'
                # The following lines may be used for testing.
                # This is the point where a manual mode may be implemented.
                # pywikibot.showDiff(text, newtext)
                # answer = raw_input(u'Mehet? ')
                # if answer != 'y':
                    # print u'Tovább.'
                    # continue
                # print 'Mentve!'
                try:
                    page.put(newtext, summary)
                    stat['moda'] += 1
                except:
                    stat['err'] += 1
            except self.MalformedPortalTemplate:
                stat['skip'] += 1
                try:
                    self.malformedTemplateWarning(page)
                    stat['moda'] += 1
                except:
                    pywikibot.output(u'Nem sikerült menteni a vitalapot: %s' %
                                     page.title())
                    stat['err'] += 1

    def addPortalToExisting(self, text):
        match = self.generalPortalRegex.search(text)
        if not match:
            raise self.MalformedPortalTemplate
        tpl = match.group()
        tplParams = match.group(1)
        pipes = tplParams.count('|')
        if '|||' in tpl or tpl.endswith('||}}') or pipes > 11:
            raise self.MalformedPortalTemplate
        if tpl.endswith('|}}') and (pipes % 2 == 0):
            raise self.MalformedPortalTemplate
        if pipes > 9:
            # This template is full, it has already 6 arguments.
            portalTemplate = u'\n{{portál|%s}}' % self.portalWithEnding
            text = text.replace(tpl, tpl + portalTemplate, 1)
            return text
        # We are ready to insert the new portal into the existing template.
        # But we still have to decide if the last existing has a suffix or not.
        if pipes % 2:
            newPart = '|' + self.portalWithEnding
        else:
            newPart = '||' + self.portalWithEnding
        newTemplate = tpl[:-2] + newPart + '}}'
        newtext = text.replace(tpl, newTemplate, 1)
        return newtext

    def addNewPortalTemplate(self, page, text):
        portalTemplate = u'{{portál|%s}}\n' % self.portalWithEnding

        # The portal template should fit in the last section.
        sectionlist = page.getSections()
        try:
            lastSectionByteOffset = sectionlist[-1][0]
        except IndexError:
            lastSectionByteOffset = 0 # No sections in the page.
        beginning = text[:lastSectionByteOffset]
        footer = text[lastSectionByteOffset:]
        # pywikibot.output(footer)

        # Try to determine the right place for the template regarding
        # [[Wikipédia:Szócikkek felépítése]].
        placeBeforeThis = ''
        minpos = sys.maxint # Upon porting to Python 3 this must be changed to sys.maxsize!
        for regex in self.followers:
            match = regex.search(footer)
            # We need the first match in the order of actual page content,
            # not the supposed order of parts.
            if match and match.start() < minpos:
                minpos = match.start()
                placeBeforeThis = match.group()
        if placeBeforeThis:
            footer = footer.replace(
                placeBeforeThis, portalTemplate + placeBeforeThis, 1)
        else:
            footer += '\n' + portalTemplate[:-1]
        # pywikibot.output(footer)

        # Go home
        newtext = beginning + footer
        return newtext

    def malformedTemplateWarning(self, page):
        talk = page.toggleTalkPage()
        try:
            text = talk.get() + '\n\n'
        except pywikibot.NoPage:
            text = ''
        text += u'==Hibás portálsablon?==\n'
        text += (u"A(z) ''%s'' portál beillesztése " % self.portal.title())
        text += u'közben a bot hibásan formázott portálsablont talált a '
        text += u'cikkben. Ez fakadhat a szócikk vagy a bot hibájából is. ~~~~'
        summary = u'/* Hibás portálsablon? */ (új szakasz)'
        talk.put(text, summary, minorEdit=False, botflag=False)

    class MalformedPortalTemplate(pywikibot.Error):
        """Indicates a malformed template that can not be handled by the bot."""

class Generator:
    """
    A page generator that may be parametrized.

    @param portal: If given, the generator checks the page for existance
                   of the given portal, and yields only pages without it.
    @type portal: unicode
    """
    def __init__(self, portal=None):
        self.portal = portal

    def pageGenerator(self):
        # Variable pass: how to identify articles.
        # Currently the behaviour is wired in.
        templatePage = pywikibot.Page(site, u'Sablon:Film infobox')
        return pagegenerators.ReferringPageGenerator(
            templatePage, onlyTemplateInclusion=True)

    def pageFilter(self):
        # Constant part: filter to main namespace, excluding pages with
        # existing template.
        gen = self.pageGenerator()
        gen = pagegenerators.NamespaceFilterPageGenerator(gen, namespaces=[0])
        if not self.portal:
            return gen
        else:
            return self.hasNoTemplate(gen)

    def hasNoTemplate(self, gen):
        for page in gen:
            text = page.get()
            if not regex(self.portal).search(text):
                yield page

def regex(portal):
    """Regex for searching the given portal as a template argument."""
    r = ur'(?is)\{\{\s*portál\s*\|[^}]*?%s[^}]*?\}\}' % portal
    return re.compile(r)

def naploz(portal): # Private
    log = munkanaplo(
        u'Portálsablonok terítése',
        u'user:BinBot/portalsablon.py',
        portal.title(),
        u'Portál:%s' % portal.title(),
        stat,
        u'automatikus',
    )
    log.run()

def main():
    global portal
    # Give the name in lower case.
    # Currently wired in together with the generator.
    portal = u'filmművészet'
    ending = ''

    # Check if the portal exists.
    p = pywikibot.Page(site, u'Portál:' + portal)
    if not p.exists():
        pywikibot.output(u'Nincs ilyen portál: %s.' % portal)
        return

    # genbot = Generator(portal)
    # For now, don't check for performance reasons, we should load the page
    # and get the text later anyway. Pre-checking is useful if we collect
    # candidates in a separate run.
    genbot = Generator()
    bot = PortalBot(genbot.pageFilter(), portal, ending)
    bot.run()

if __name__ == '__main__':
    try:
        main()
    finally:
        naploz(portal) # Private
        pywikibot.stopme()

Ideiglenes tárolóhely, amíg archiválódnak a szakaszokSzerkesztés