Usuari:CobainBot/scripts/cawiki/viquiprojectes.py

import json
import json
import re
from datetime import datetime
from dateutil.parser import parse as parse_datetime
from pathlib import Path
from typing import List, NoReturn, Optional

from pywikibot import Site, Page, Category


# globals
site = Site('ca', 'wikipedia', 'TronaBot')


class Article(Page):
    def __init__(self, title, user: 'User'):
        Page.__init__(self, site, title)
        self.user = user
        self.size = 0

    def growth(self):
        return self.size


class User:
    def __init__(self):
        self.name = ''
        self.growth = 0


class Setup:
    """
    Classe per obtenir la configuració del Viquiprojecte.

    La pàgina de configuració és un wikitext prou interpretable tant per a un humà com per al bot. Es pretén que
    qualsevol puga comprendre la configuració que se li dóna al bot.
    """
    def __init__(self, title):
        self.page = Page(site, title)
        self.content = self.page.text
        self.categories: List[str] = []
        self.requested_articles: List[Page] = []
        self.candidates: List[User] = []
        self.start_dt: Optional[datetime] = None
        self.end_dt: Optional[datetime] = None
        self.debug = False

    def load(self, topic) -> NoReturn:
        """
        Obtenció de dades emmagatzemades a un fitxer.
        Només s'hauria de recòrrer a este mètode per a agilitzar l'obtenció d'estes dades quan
        estem fent proves o en mode de depuració (debug = True).
        :param topic: és el attribut de classe que hem emmagatzemat
        :return:
        """
        with open(f'resources/vp_comics_{topic}.json', 'r') as fp:
            data = json.load(fp)

        if hasattr(self, topic):
            setattr(self, topic, data)

    def save(self, topic) -> NoReturn:
        """
        Emmagatzemament de dades per agilitzar la càrrega en successives execucions.
        :param topic:
        :return:
        """
        data = getattr(self, topic)
        with open(f'resources/vp_comics_{topic}.json', 'w') as fp:
            json.dump(data, fp)

    def set_datetimes(self) -> NoReturn:
        """
        Obtenim dates d'inici i de finalització del Viquiprojecte.
        """
        datetimes=[]
        for match in re.finditer(
                r'(?P<datetimezone>\d{4}(?P<datesep>[/-])\d{1,2}(?P=datesep)\d{1,2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2})', self.content):
            datetimes.append(parse_datetime(match.group('datetimezone')))
        self.start_dt = min(datetimes)
        self.end_dt = max(datetimes)
        print(f'start datetime: {self.start_dt:%Y-%m-%d %H:%M:%S}\nend datetime: {self.end_dt:%Y-%m-%d %H:%M:%S}')

    def load_categories(self) -> NoReturn:
        """
        Per agilitzar la càrrega de categories, llegim de fitxer o recorrem recursivament les supercategories.
        """
        if self.debug and Path(f'resources/vp_comics_categories.json').exists():
            self.load('categories')
            print(f'{len(self.categories)} categories has been loaded:\n{self.categories}')
        else:
            self.set_categories()

    def set_categories(self) -> NoReturn:
        """
        Recorrem les supercategories i les inserim en un fitxer per facilitar la càrrega més ràpida.
        """
        for item in re.finditer(r'\*{2}\s*(?P<category>Categoria:.+)', self.content):
            category = Category(site, item.group('category'))
            self.categories.append(category.title(with_ns=False, without_brackets=False))
            for cat in category.subcategories(recurse=True):
                self.categories.append(cat.title(with_ns=False, without_brackets=False))
        self.categories = list(set(self.categories))
        self.categories.sort()
        self.save('categories')

    def set_requested_articles(self) -> NoReturn:
        """
        Obtenim els articles sol·licitats.
        """
        match = re.search(r'\*\* (?P<title>Portal:+[^/]+/Sol·licitats)', self.content)
        if match:
            page = Page(site, match.group('title'))
            for match in re.finditer(r'\*\s*\[\[(?P<title>[^\]]+)\]\]', page.text):
                self.requested_articles.append(match.group('title'))
        print(f'{len(self.requested_articles)} articles found')

    def run(self):
        self.set_datetimes()
        self.load_categories()
        self.set_requested_articles()


class Wikiproject:
    """
    Classe pensada per a poder fer recompte de qualsevol Viquiprojecte.
    En esta primera entrega ens centrem amb el Viquiprojecte:Còmics 2023

    Carreguem la configuració a la pàgina Viquiprojecte:*/config
    Els resultats en pengen a Viquiprojecte:*/Seguiment

    Els participants els obtenim de cada secció de Viquiprojecte:*/Seguiment.
    Analitzem cada contribució que fan dins de les dates assignades a l'espai principal
    només si pertany a alguna de les supercategories o llurs subcategories.

    Aleshores ens interessa saber:
     - el nombre d'octets afegits
     - si és article nou
        [cal tenir en compte que no ens interessa saber el creador si l'article fou creat anteriorment a les dates]
     - si s'ha extret alguna plantilla de manteniment
        [sugg.: el bot no hauria de comptar els octets que ocupen estes plantilles, algorisme complex!]
    """
    def __init__(self, project_name='Còmics 2023'):
        self.init_time = datetime.now()
        site.login()
        self.main_page = Page(site, f'Viquiprojecte:{project_name}')
        self.setup_title = f'Viquiprojecte:{project_name}/config'
        self.contestant_title = f'Viquiprojecte:{project_name}/Seguiment'
        self.result_title = f'Viquiprojecte:{project_name}/resultats automatitzats'
        self.debug = False

    def run(self):
        print(f'[{self.init_time:%H:%M:%S}] started')
        setup = Setup(self.setup_title)
        setup.debug = self.debug
        setup.run()


if __name__ == '__main__':
    viquiprojecte = Wikiproject()
    viquiprojecte.debug = True
    viquiprojecte.run()