import re
import pywikibot
from pywikibot import pagegenerators
SITE = pywikibot.Site("fr", "wikipedia")
TARGET_HOST = "l2tc.com"
EDIT_SUMMARY = (
"Retrait de liens externes/références vers l2tc.com (site blacklisté) "
"([[Wikipédia:Bot/Requêtes|requête bot]])"
)
PAGE_LIST = [
""
]
# ---------------------------------------------------------------------------
# Regex
# ---------------------------------------------------------------------------
# (?<!/)> : exclut les balises autofermantes <ref name=foo />
# sans quoi elles avalent la ligne suivante jusqu'au prochain </ref>
RE_REF_DEF = re.compile(
r'<ref(?P<attrs>[^>]*?)(?<!/)>(?P<content>.*?)</ref>',
re.DOTALL | re.IGNORECASE,
)
# Toute ligne de puce contenant l2tc.com (filtrée ensuite par _line_has_bare_l2tc)
RE_BULLET_CANDIDATE = re.compile(
r'^\*[^\n]*' + re.escape(TARGET_HOST) + r'[^\n]*\n?',
re.IGNORECASE | re.MULTILINE,
)
def _extract_name(attrs: str) -> str | None:
"""Extrait le name d'une balise <ref>, avec ou sans guillemets."""
m = re.search(r'\bname\s*=\s*(["\']?)(?P<n>[^"\'>\s/]+)\1', attrs, re.IGNORECASE)
return m.group("n") if m else None
def _re_call(name: str) -> re.Pattern:
"""Regex pour les appels <ref name="foo" /> (toutes variantes de guillemets)."""
return re.compile(
r'<ref\s+name\s*=\s*(["\']?)' + re.escape(name) + r'\1\s*/\s*>',
re.IGNORECASE,
)
def _line_has_bare_l2tc(line: str) -> bool:
"""True si l2tc.com apparaît dans la ligne EN DEHORS de toute balise <ref>."""
masked = re.sub(r'<ref[^>]*(?<!/)>.*?</ref>', '', line, flags=re.IGNORECASE | re.DOTALL)
masked = re.sub(r'<ref\b[^>]*/>', '', masked, flags=re.IGNORECASE)
return TARGET_HOST in masked.lower()
# ---------------------------------------------------------------------------
# Nettoyage
# ---------------------------------------------------------------------------
def clean_text(text: str) -> tuple[str, list[str]]:
log: list[str] = []
names_to_delete: set[str] = set()
work = text
# ── Étape 1 : supprimer les définitions <ref>…l2tc…</ref> ────────────
# On relance la recherche après chaque suppression pour garder des
# positions toujours valides dans `work`.
while True:
found = False
for m in RE_REF_DEF.finditer(work):
if TARGET_HOST not in m.group("content").lower():
continue
found = True
log.append("Référence l2tc supprimée.")
name = _extract_name(m.group("attrs"))
if name:
names_to_delete.add(name)
log.append(f" → Nom retenu : {name}")
work = work[:m.start()] + work[m.end():]
break # positions invalides : on repart du début
if not found:
break
# ── Étape 2 : supprimer les appels <ref name="foo" /> associés ───────
for name in names_to_delete:
cr = _re_call(name)
n = len(cr.findall(work))
if n:
log.append(f" → {n} appel(s) de '{name}' supprimé(s)")
work = cr.sub("", work)
# ── Étape 3 : supprimer les puces * contenant un lien l2tc nu ────────
# (cas liens externes sans balise <ref>)
def _replace_bare_bullet(m):
if _line_has_bare_l2tc(m.group(0)):
log.append("Puce (* l2tc.com) supprimée.")
return ""
return m.group(0)
work = RE_BULLET_CANDIDATE.sub(_replace_bare_bullet, work)
return work, log
# ---------------------------------------------------------------------------
# Générateur de pages
# ---------------------------------------------------------------------------
def get_pages(local_args: list) -> pagegenerators.GeneratorFactory:
gf = pagegenerators.GeneratorFactory(site=SITE)
gf.handle_args(local_args)
gen = gf.getCombinedGenerator(preload=True)
if gen:
return gen
pywikibot.output(f"Utilisation de la liste intégrée ({len(PAGE_LIST)} articles).")
return pagegenerators.PreloadingGenerator(
iter(pywikibot.Page(SITE, t) for t in PAGE_LIST)
)
# ---------------------------------------------------------------------------
# Point d'entrée
# ---------------------------------------------------------------------------
def main(*args):
local_args = pywikibot.handle_args(args)
total = treated = changed = skipped = 0
for page in get_pages(local_args):
total += 1
pywikibot.output(f"\n{'─'*60}\n[{total}] {page.title()}")
try:
original = page.text
except pywikibot.exceptions.NoPageError:
pywikibot.warning("Page inexistante.")
skipped += 1
continue
if TARGET_HOST not in original:
pywikibot.output("Aucun lien – skip.")
continue
treated += 1
cleaned, entries = clean_text(original)
if not entries or cleaned == original:
pywikibot.output("Lien présent mais structure non reconnue.")
skipped += 1
continue
changed += 1
for e in entries:
pywikibot.output(f" • {e}")
try:
page.text = cleaned
page.save(summary=EDIT_SUMMARY, minor=True, watch="nochange")
pywikibot.output(" ✓ Sauvegardé.")
except pywikibot.exceptions.EditConflictError:
pywikibot.warning(" Conflit d'édition.")
skipped += 1
except pywikibot.exceptions.LockedPageError:
pywikibot.warning(" Page protégée.")
skipped += 1
except Exception as exc:
pywikibot.error(f" Erreur : {exc}")
skipped += 1
pywikibot.output(
f"\n{'='*60}\n"
f"Parcourues : {total} | avec l2tc.com : {treated} "
f"| modifiées : {changed} | ignorées : {skipped}"
)
if __name__ == "__main__":
main()