Участник:BsivkoBot/Категоризатор

Описание

Мотивация

Бот с аналогичной функциональностью есть здесь: Участник:KrBot/Задания

Реализация

Согласно плану задач, категоризатор периодически сканирует настройки планировщика и запускает обработку согласно найденным задачам. Планировщик определяет предел числа обработанных статей за раз и период сканирования.

Реализовано с помощью модуля pywikibot textlib, и поэтому категоризатор автоматически правит формат перед категориями (одна пустая строка).

Конфигурация описана на следующей странице:

Пример конфигурации:

{
  "переименовать": {
    "Винтик": "Шпунтик"
  },
  "удалить": [
    "Ёжики в тумане"
  ]
}

То есть, категоризатор будет искать все страницы категорий «Винтик» и «Ёжики в тумане», первые из которых переименовываются в «Шпунтик», а вторые удаляются.

После выполнения задачи не забываем убрать из конфигурации установленные параметры.

Исходный код

import json

import mwparserfromhell
import pywikibot

from bot_logging.log import log_warning_json
from wiki_category_processing.category_collector import rename_category, delete_category
from wiki_requests.site import ru_site
from wiki_template_processing.endings_format import detect_format_endings, set_format_ending

TASK_CATEGORIZATOR = u"категоризатор"
categorizator_cfg_cached = None


def get_catogorizator_cfg():
    global categorizator_cfg_cached
    if categorizator_cfg_cached is not None:
        return categorizator_cfg_cached

    pagename = "Участник:BsivkoBot/Категоризатор/Конфигурация"

    site = pywikibot.Site()
    page = pywikibot.Page(site, pagename)
    text = str(page.text)
    categorizator_cfg_cached = json.loads(text)
    return categorizator_cfg_cached


def get_single_or_list_as_list(value):
    if isinstance(value, list):
        return value

    if value is None:
        return value

    return [value]


def categorizator_for_article(text, cfg, title="unknown"):

    if cfg.get("переименовать") is not None:
        to_rename = cfg["переименовать"]
        for key, value in to_rename.items():
            text = rename_category(ru_site(), text, "Категория:" + key, "Категория:" + value)

    if cfg.get("удалить") is not None:
        to_delete = cfg["удалить"]
        for name in to_delete:
            text = delete_category(ru_site(), text, "Категория:" + name)

    return text

category_collector.py:

import mwparserfromhell
import pywikibot
from pywikibot import textlib

from wiki_requests.redirects import get_redirects_to_template


def collect_categories(text):
    result = set()

    parsed = mwparserfromhell.parse(text)

    for wikilink in parsed.filter_wikilinks():
        if wikilink.title is not None:
            title = str(wikilink.title)
            cat_prefix = "Категория:"
            if len(title) > len(cat_prefix):
                if title[:len(cat_prefix)] == "Категория:":
                    result.add(title[len(cat_prefix):])

    return result


def one_of_categories_is_present(text, names):

    parsed = mwparserfromhell.parse(text)

    categories_set = collect_categories(text)

    categories_set = [element.lower() for element in categories_set]

    for name in names:
        if name.lower() in categories_set:
            return True

    return False


def delete_category(site, text, name_to_delete):
    categories = textlib.getCategoryLinks(text, site=site)
    for cat in categories:
        if cat.title() == name_to_delete:
            categories.remove(cat)
    text = textlib.replaceCategoryLinks(text, categories, site)
    return text


def rename_category(site, text, name_to_rename, new_name):
    categories = textlib.getCategoryLinks(text, site=site)
    catpl = pywikibot.Category(site, new_name)
    for cat in categories:
        if cat.title() == name_to_rename:
            categories.remove(cat)
            categories.append(catpl)
    text = textlib.replaceCategoryLinks(text, categories, site)
    return text

Тестирование

from wiki_category_processing.category_collector import collect_categories, one_of_categories_is_present, \
    delete_category, rename_category
from wiki_requests.site import ru_site


def test_collect_categories():

    text = "{{компьютерная игра}} [[Категория:Q]]\n[[Категория:QWERT]]"

    value = collect_categories(text)

    assert value.intersection({'Q'}) == {'Q'}
    assert value.intersection({'QWERT'}) == {'QWERT'}
    assert value.intersection({'видеоигра'}) == set()

    return


def test_category_is_present():

    text = "{{компьютерная игра}} [[Категория:Q]]\n[[Категория:QWERT]]"

    assert one_of_categories_is_present(text, ["компьютерная игра"]) == False
    assert one_of_categories_is_present(text, ["Q"]) == True
    assert one_of_categories_is_present(text, ["Q", "QWERT"]) == True
    assert one_of_categories_is_present(text, ["A", "B", "Q"]) == True
    assert one_of_categories_is_present(text, ["A", "B", "C"]) == False

    return


def test_delete_category():
    text = "{{компьютерная игра}} [[Категория:Q]]\n[[Категория:QWERT]]"

    value = delete_category(ru_site(), text, "Категория:Q")
    assert "Q]]" not in value

    value = delete_category(ru_site(), text, "Категория:QWERT")
    assert "QWERT" not in value

    return


def test_rename_category():
    text = "{{компьютерная игра}} [[Категория:Q]]\n[[Категория:QWERT]]"

    value = rename_category(ru_site(), text, "Категория:Q", "Категория:A")
    assert "Q]]" not in value
    assert "A]]" in value

    return

Состояние и развитие

  • Тестирование. Bsivko (обс.) 06:14, 1 ноября 2019 (UTC)

Обсуждение