Wikiproyecto:Bots/Repositorio/RCAD.py

De Wikipedia, la enciclopedia libre

actualizar · discusión · código desprotegido  

Información de fichero
  • Nombre del fichero: RCAD.py
  • Lenguaje: Python
  • Estado: no protegido
Detalles de edición
  • Detalles:
Script para actualizar la plantilla {{Recordatorio CAD}}, Wikipedia:Candidatos a artículos destacados/Número y Wikipedia:Candidatos a artículos destacados, originalmente de EmBOTellado (disc. · contr. · bloq.). Actualmente actualizada por ChessBOT (disc. · contr. · bloq.) y TronaBot (disc. · contr. · bloq.), y previamente por SebrevBOT (disc. · contr. · bloq.), AstaBOTh15 (disc. · contr. · bloq.), SamuraiBot (disc. · contr. · bloq.), Botito777 (disc. · contr. · bloq.), BOTzilla (disc. · contr. · bloq.), Gizbot (disc. · contr. · bloq.) y Fidelbotquegua (disc. · contr. · bloq.). A notar la reestructuración y mejora del script por Coet (disc. · contr. · bloq.).
# -*- coding: utf-8 -*-
import locale, os, re, sys
from datetime import datetime, timedelta

sys.path.append("/shared/pywikipedia/core")
from pywikibot import Category, Site, output, Page, showDiff as show_diff
from pywikibot import handleArgs as handle_args, NoPage
from pywikibot.data import api

class Templates(object):
	"""Este objeto extrae todas las plantillas de un artículo con cada parámetro por medio
	del pywikibot.
	Hay un bug cuando se trata de obtener los parámetros de plantillas con \n| (29-07-2014).
	"""
	def __init__(self, source):
		self._source = source

	def templates_with_params(self):
		self._templates = [(o[0].title(withNamespace=False), ["=" in p and (p.split("=",1)[0].strip(), p.split("=",1)[1].strip()) or (o[1].index(p), p.strip()) for p in o[1]]) for o in self._source.templatesWithParams()]
		return self._templates

	def has_template(self, tname):
		"""Devuelve el número de veces que aparece una plantilla."""
		tname = capitalize(tname)
		if not hasattr(self, "_templates"):
			self.templates_with_params()
		c=0
		for template, params in self._templates:
			if template == tname:
				c+=1
		return c

	def get_template_param(self, tname, param):
		"""Devuelve totas las plantillas 'tname' con el parámetro 'param'"""
		tname = capitalize(tname)
		if not hasattr(self, "_templates"):
			self.templates_with_params()
		templates=[]
		if isinstance(param, int):
			param = param-1

		for template, params in self._templates:
			if template == tname:
				if not isinstance(param, basestring):
					if len(params)-1 <= param:
						templates.append((template, params[param][1]))
				else:
					for items in params:
						if items[0] == param:
							templates.append((template, items[1]))
		return templates

class Usuario(object):
	def __init__(self, nick):
		self.nick = nick
		params = {
			"action": "query",
			"list": "users",
			"ususers": nick,
			"usprop": "groups",
		}
		q = api.Request(site, **params)
		self.data = q.submit()

	@property
	def is_bot(self):
		if self.is_anon: return False
		if self.data['query'].has_key('users'):
			if self.data['query']['users'][0].has_key('groups'):
				user = self.data["query"]["users"][0]
				return "bot" not in user['groups']
			else:
				output(u"Ha sido imposible obtener grupos de usuario para %s" % self.data['query']['users'][0]['name'])

	@property
	def is_anon(self):
		return self.data["query"]["users"][0].has_key("invalid")

class Candidato_CAD(object):
	u"""Este objeto obtiene los datos de cada artículo candidato."""
	def __init__(self, articulo):
		self._articulo = articulo
		self._pagina = Page(site, u"Wikipedia:Candidatos a artículos destacados/%s" % articulo)
		self._contenido = self._pagina.text
		self._primera_edicion = self._pagina.oldest_revision #revid, user, timestamp, comment
		self._ultima_edicion = self._pagina.latest_revision
		self._primera_fecha = self._pagina.oldest_revision.timestamp
		self._ultima_fecha = self._pagina.latest_revision.timestamp

	def participacion(self):
		"""Determina el grado de participación."""
		diferencia_fecha = self._ultima_fecha - self._primera_fecha
		dias_sin_participar = datetime.today() - self._ultima_fecha
		#si lleva al menos un día abierta pero sin editarse nada
		if (self._primera_edicion == self._ultima_edicion) and (dias_sin_participar > timedelta(days=1)):
				return 0
		#si se ha llegado a publicar pero sin más ediciones, o ya pasan tres días desde la última edición
		elif (self._primera_fecha != self._ultima_fecha) or (diferencia_fecha > timedelta(days=3)):
				if dias_sin_participar > timedelta(days=3):
						return 0
				elif dias_sin_participar <= timedelta(days=2):
						return 2
				#obviously dias_sin_participar == 3
				else:
						return 1
		elif self._primera_fecha == self._ultima_fecha == datetime.today():
				return ""
		return ""

	def participantes(self):
		"""Obtiene el número de participantes en la sección Opiniones"""
		usuarios = set()
		for edicion in self._pagina.revisions():
			#if edicion.anon: continue
			if edicion.minor: continue
			elif Usuario(edicion.user).is_bot: continue
			usuarios.add(edicion.user)
		return len(usuarios)

	def observaciones(self):
		"""Determina el valor de la columna 'observaciones'."""
		if re.search("(?<!<s>)\{\{Cierre ACAD.*\}\}", self._contenido):
				return "24h"
		if (self._primera_fecha == self._ultima_fecha) or ((self._ultima_fecha - self._primera_fecha) <= timedelta(days=3)):
				return "Nueva"
		elif (datetime.today() - self._ultima_fecha) > timedelta(days=3):
				return "Emergencia"
		return ""

	def datos(self):
		"""Obtenemos los siguientes datos: artículo, proponente, mes, tamaño, votos pro, votos contra y fecha cierre"""
		locale.setlocale(locale.LC_ALL, sys.platform.startswith("win") and "Spanish_Spain" or "es_ES.UTF8")
		#Por defecto (en caso de no poder extraer el proponente expuesto), el proponente
		#será el creador de la candidatura.
		proponente = self._primera_edicion.user
		m = re.search(r";Propuesto por:([^\n]+)\n", self._contenido)
		if m:
			m = re.search(ur"\[\[(?:[Uu]suario(?:[_ ][Dd]iscusión)?|[Uu]ser(?:[_ ][Tt]alk)?):([^\]|]+)(?:\|[^\]]+)?\]\]", m.group(1))
			if m:
				proponente = m.group(1).strip()
		#La fecha que debe constar es mes y año, la de la creación de la página es perfecta (¡y fuera regexp!)
		fecha = timestamp(self._primera_fecha.isoformat())
		fecha = datetime.strftime(fecha, "%B de %Y")
		#Obtenemos los bytes del artículo mediante una consulta API.
		params = {
			"action": "query",
			"titles": self._articulo,
			"prop": "revisions",
			"rvprop": "size",
			"indexpageids": ""
		}
		q = api.Request(site, **params)
		data = q.submit()
		pageid = data["query"]["pageids"][0]
		tamano = data['query']['pages'][pageid]['revisions'][0]['size']
		#Recuento de votos.
		#TODO: aquí faltan plantillas...
		#(Nota: un page.templates() no descartaría las eliminadas con <s></s>.)
		votos_pro = re.findall("(?<!<s>)(?:\{\{ *(?:\+|[Aa][_ ]?favor|(?:[Mm]uy|[Aa]lgo|[Vv]otos)[_ ]a[_ ]favor|[Ss]upport) *\}\})", self._contenido)
		votos_contra = re.findall("(?<!<s>)(?:\{\{ *(?:-|[Ee]n[_ ]?contra|(?:[Mm]uy|[Aa]lgo|[Vv]otos)[_ ]en[_ ]contra|[Dd]enegado) *\}\})", self._contenido)
		#TODO: fecha de cierre?
		cierre = u"—"
		plantillas = Templates(self._pagina)
		c = plantillas.get_template_param("Cierre ACAD", 2)
		cierre = ("desfavorable","favorable","desestimado")[int(c[0][1])] if c and c[0][1].isdigit() else u"—"
		return proponente, fecha, "{:n} bytes".format(tamano), len(votos_pro), len(votos_contra), cierre

class Candidato_VAD(object):
	u"""Este objeto obtiene los datos de cada artículo candidato."""
	def __init__(self, articulo):
		self._articulo = articulo
		self._pagina = Page(site, u"Wikipedia:Candidatos a artículos destacados/%s" % articulo)
		self._pagina_disc = Page(site, u"Wikipedia discusión:Candidatos a artículos destacados/%s" % articulo)
		self._historial = self._pagina.getVersionHistory(step=500)
		self._contenido = self._pagina.text
		self._primera_edicion = self._historial[-1]
		self._ultima_edicion = self._historial[0]
		self._primera_fecha = self._primera_edicion[1]
		self._ultima_fecha = self._ultima_edicion[1]

	def participacion(self):
		"""Determina el grado de participación."""
		diferencia_fecha = self._ultima_fecha - self._primera_fecha
		dias_sin_participar = datetime.today() - self._ultima_fecha
		#si lleva al menos un día abierta pero sin editarse nada
		if (self._primera_edicion == self._ultima_edicion) and (dias_sin_participar > timedelta(days=1)):
				return 0
		#si se ha llegado a publicar pero sin más ediciones, o ya pasan tres días desde la última edición
		elif (self._primera_fecha != self._ultima_fecha) or (diferencia_fecha > timedelta(days=3)):
				if dias_sin_participar > timedelta(days=3):
						return 0
				elif dias_sin_participar <= timedelta(days=2):
						return 2
				#obviously dias_sin_participar == 3
				else:
						return 1
		elif self._primera_fecha == self._ultima_fecha == datetime.today():
				return ""
		return ""

	def participantes(self):
		"""Obtiene el número de participantes. (Equivalente a len(self._pagina.contributingUsers()).)"""
		usuarios = set()
		for edicion in self._historial:
		   usuarios.add(edicion[2])
		return len(usuarios)

	def observaciones(self):
		"""Determina el valor de la columna 'observaciones'."""
		if re.search("(?<!<s>)\{\{Cierre ACAD.*\}\}", self._contenido):
				return "24h"
		if (self._primera_fecha == self._ultima_fecha) or ((self._ultima_fecha - self._primera_fecha) <= timedelta(days=3)):
				return "Nueva"
		elif (datetime.today() - self._ultima_fecha) >= timedelta(days=3):
				return "Emergencia"
		return ""

	def datos(self):
		"""Obtenemos los siguentes datos: artículo, proponente, mes, tamaño, votos pro, votos contra y fecha cierre"""
		locale.setlocale(locale.LC_ALL, sys.platform.startswith("win") and "Spanish_Spain" or "es_ES.UTF8")
		#Por defecto (en caso de no poder extraer el proponente expuesto), el proponente
		#será el creador de la candidatura.
		proponente = self._primera_edicion[2]
		m = re.search(r";Propuesto por:([^\n]+)\n", self._contenido)
		if m:
			m = re.search(ur"\[\[(?:[Uu]suario(?:[_ ][Dd]iscusión)?|[Uu]ser(?:[_ ][Tt]alk)?):([^\]|]+)(?:\|[^\]]+)?\]\]", m.group(1))
			if m:
				proponente = m.group(1).strip()
		#La fecha que debe constar es mes y año, la de la creación de la página es perfecta (¡y fuera regexp!)
		fecha = timestamp(self._primera_edicion[1].isoformat())
		fecha = datetime.strftime(fecha, "%B de %Y")
		#Obtenemos los bytes del artículo mediante una consulta API.
		params = {
			"action": "query",
			"titles": self._articulo,
			"prop": "revisions",
			"rvprop": "size",
			"indexpageids": ""
		}
		q = api.Request(site, **params)
		data = q.submit()
		pageid = data["query"]["pageids"][0]
		tamano = data['query']['pages'][pageid]['revisions'][0]['size']
		#Recuento de votos.
		#TODO: aquí faltan plantillas...
		#(Nota: un page.templates() no descartaría las eliminadas con <s></s>.)
		votos_pro = re.findall("(?<!<s>)(?:\{\{ *(?:\+|[Aa][_ ]?favor|(?:[Mm]uy|[Aa]lgo|[Vv]otos)[_ ]a[_ ]favor|[Ss]upport) *\}\})", self._contenido)
		votos_contra = re.findall("(?<!<s>)(?:\{\{ *(?:-|[Ee]n[_ ]?contra|(?:[Mm]uy|[Aa]lgo|[Vv]otos)[_ ]en[_ ]contra|[Dd]enegado) *\}\})", self._contenido)
		#TODO: fecha de cierre?
		cierre = u"—"
		plantillas = Templates(self._pagina)
		c = plantillas.get_template_param("Cierre ACAD", 2)
		cierre = ("desfavorable","favorable","desestimado")[int(c[0][1])] if c and c[0][1].isdigit() else u"—"
		return proponente, fecha, "{:n} bytes".format(tamano), len(votos_pro), len(votos_contra), cierre

class Candidatos(object):
	"""Este objeto recopila todos los candidatos. Actualiza los datos de los artículos
	de las tablas de [[Wikipedia:Candidatos a artículos destacados/Nominaciones]]
	"""
	def __init__(self):
		self._pagina = Page(site, u"Wikipedia:Candidatos a artículos destacados/Nominaciones")
		self._contenido = self._contenido_original = self._pagina.text
		self._raiz = u"Wikipedia:Candidatos a artículos destacados"
		self._re_raiz = ur"(?::?Wikipedia:Candidatos a artículos destacados)??"
		patente = ur"\{\{%s/Temáticas\|(?P<art>[^|]+?)\|(?P<prop>[^|]+?)\|(?P<fecha>[^|]*?)\|(?P<tam>[^|]*?)\|(?P<fav>[^|]*?)\|(?P<cnt>[^|]*?)\|(?P<res>[^|]*?)\}\}" % self._re_raiz
		patente = ur"\{\{%s/Temáticas(VAD)?\|([^|]+)\|[^}]+\}\}" % self._re_raiz
		comentarios_fuera = re.sub("<!--.+?-->", "", self._contenido, flags=re.S | re.M)
		self._elementos = re.findall(patente, comentarios_fuera, re.I)
		verbose("t: %i | sct: %i | e:%r" % (len(self._contenido), len(comentarios_fuera), len(self._elementos)), 3)

	def principal(self):
		"""Actualiza [[Wikipedia:Candidatos a artículos destacados/Registro de errores]] y
		prepara actualización de [[Wikipedia:Candidatos a artículos destacados]]"""
		categoria = Category(site, u"Categoría:Wikipedia:Candidatos a artículos destacados")
		paginas = categoria.articles()

		#revisamos el contenido
		candidatos = []
		for pagina in paginas:
			if pagina.namespace() == 1:
				candidatos.append(pagina.title(withNamespace=False))

		#TODO:
		#los que faltan, son 'c. retirados' y debe retirarse la plantilla {{Candidato a destacado}} de la discusión?
		#los que sobran, deben retirarse de [[Wikipedia:Candidatos a artículos destacados/Nominaciones]] al haber  sido retirados?
		elementos = [c if isinstance(c, basestring) else c[1] for c in self._elementos]
		faltan = [u"{{A|%s}}" % candidato for candidato in candidatos if candidato not in elementos]
		sobran = [u"{{A|%s}}" % candidato for candidato in elementos if candidato not in candidatos]
		texto=""
		if faltan:
			texto += u"== faltan ==\nArtículos que '''no''' figuran en [[Wikiped"
			texto += u"ia:Candidatos a artículos destacados/Nominaciones]], pero tienen {{tl|"
			texto += u"Candidato a destacado}} en su página de discusión.\n\n"
			texto += "*%s" % "\n*".join(faltan)
			texto += "\n"
		if sobran:
			if texto: texto += "\n"
			texto += u"== sobran ==\nArtículos que figuran en [[Wikipedia:Candid"
			texto += u"atos a artículos destacados/Nominaciones]], pero no tienen {{tl|Candid"
			texto += u"ato a destacado}} en su página de discusión.\n\n"
			texto += "*%s" % "\n*".join(sobran)
			texto += "\n"
		if texto:
			verbose(texto,2)

		pagina_err = Page(site, u"Wikipedia:Candidatos a artículos destacados/Registro de errores")
		if not args.test and texto != pagina_err.text:
			pagina_err.text = texto
			pagina_err.save(
				"Bot: actualizando datos, %i no figuran en [[WP:CAD]] y %i "
				"sin {{Candidato a destacado}} [total: %i]" % (
					len(faltan),
					len(sobran),
					len(faltan)+len(sobran)
				)
			)

		expreg_busqueda = re.compile(ur"\{\{%s/Temáticas\|(?P<art>[^|]+)\|(?P<prop>[^|]+)\|(?P<tam>[^|]+)\|(?P<fech>[^|]+)\|(?P<fav>[^|]+)\|(?P<con>[^|]+)\|(?P<cie>[^}]+)\}\}" % self._re_raiz)
		expreg_busqueda_vad = re.compile(ur"\{\{%s/TemáticasVAD\|(?P<art>[^|]+)\|(?P<prop>[^|]+)\|(?P<fech>[^|]+)\|(?P<tam>[^|]+)\|(?P<rev>[^|]+)\|(?P<vot>[^|]+)\|(?P<cie>[^}]+)\}\}" % self._re_raiz)
		patron_reemplazo = ur"{{{{%s/Temáticas|{art}|{prop}|{tam}|{fech}|{fav}|{con}|{cie}}}}}\n" % self._raiz
		patron_reemplazo_vad = ur"{{{{%s/TemáticasVAD|{art}|{prop}|{fech}|{tam}|{rev}|{vot}|{cie}}}}}\n" % self._raiz
		cand_dict = dict([(d[0],dict(zip(("prop",'tam',"fech",'fav',"con",'cie'),(d[1],d[2],d[3],d[4],d[5],d[6])))) for d in expreg_busqueda.findall(self._contenido)])
		candvad_dict = dict([(d[0],dict(zip(("prop","fech",'tam','rev',"vot",'cie'),(d[1],d[2],d[3],d[4],d[5],d[6])))) for d in expreg_busqueda.findall(self._contenido)])
		#nuevo_cand_dict=dict([(c,dict(zip(("prop","tam","fech","fav","con","cie"),Candidato(c).datos()))) for c in self._elementos])
		nuevo_canddict={}; nuevo_candvad_dict={}; i=0
		return
		for vad, candidato in self._elementos:
			#parche temporal bug candidato sin página cadidatura --coet 2014-11-21
			if not vad:
				try:
					cand = Candidato_CAD(candidato)
				except NoPage:
					cand = lambda x: x
					cand.datos = lambda: ("sin datos", "0", "sin datos", 0,0,"sin datos")
				nuevo_canddict[candidato] = dict(zip(("prop","tam","fech","fav","con","cie"),cand.datos()))
				if not cand_dict.has_key(candidato):
					output(u"%s no està en candidatos" % candidato)
				else:
					i+=1
					output(u"%i [[%s]]" % (i, candidato))
					self._contenido = re.sub(
						ur"\{\{%s/Temáticas *\| *%s *\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|[^}]+\}\}[\r\n\s]+" % (self._re_raiz, re.escape(candidato)),
						patron_reemplazo.format(art=candidato, **nuevo_canddict[candidato]),
						self._contenido
					)
			else:
				try:
					cand = Candidato_VAD(candidato)
				except NoPage:
					cand = lambda x: x
					cand.datos = lambda: ("sin datos", "0", "sin datos", 0,0,"sin datos")
				nuevo_candvad_dict[candidato] = dict(zip(("prop","fech","tam","rev","vot","cie"),cand.datos()))
				if not candvad_dict.has_key(candidato):
					output(u"%s no està en candidatos" % candidato)
				else:
					i+=1
					output(u"%i [[%s]]" % (i, candidato))
					self._contenido = re.sub(
						ur"\{\{%s/TemáticasVAD *\| *%s *\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|[^}]+\}\}[\r\n\s]+" % (self._re_raiz, re.escape(candidato)),
						patron_reemplazo.format(art=candidato, **nuevo_canddict[candidato]),
						self._contenido
					)


	def recuento(self):
		"""Prepara el recuento de las cabeceras."""
		secciones_original = dict([(seccion[0], seccion[1].count(u"{{/Temáticas")) for seccion in re.findall(r"\{\{/Cabecera\|([^|]+)\|\d+ *\}\}(.*?\|\})", self._contenido_original, re.S)])
		secciones = dict([(seccion[0], seccion[1].count(u"{{/Temáticas")) for seccion in re.findall(r"\{\{/Cabecera\|([^|]+)\|\d+ *\}\}(.*?\|\})", self._contenido, re.S)])
		totales = dict([(seccion, (secciones_original[seccion], secciones[seccion])) for seccion in secciones_original.keys()])
		total = 0
		for seccion in totales:
			total += totales[seccion][1]
			if totales[seccion][0] != totales[seccion][1]:
				self._contenido = re.sub(
					ur"\{\{/Cabecera\|%s\|%i\}\}" % (seccion, totales[seccion][0]),
					u"{{/Cabecera|%s|%i}}" % (seccion, totales[seccion][1]),
					self._contenido
				)
		print "Total candidaturas: %i"%total

	def editar(self):
		"""Actualiza [[Wikipedia:Candidatos a artículos destacados]]."""
		output("\n%s\n" % " cambios ".center(30, "="))
		show_diff(self._contenido_original, self._contenido)
		if not args.test and self._contenido_original != self._contenido:
			self._pagina.text = self._contenido
			self._pagina.save(u"Bot: actualizando tablas.")

	def inicio(self):
		self.principal()
		self.recuento()
		self.editar()

class Recordatorio(object):
	"""Este objeto sirve para actualizar el contenido de la plantilla
	{{Recordatorio CAD}} y el contador de [[Wikipedia:Candidatos a artículos destacados/Número]]
	"""
	def __init__(self):
		self._pagina = Page(site, "Plantilla:Recordatorio CAD")
		self._contenido = self._contenido_original = self._pagina.text
		self._candidatos = candidatos._elementos
		self.recuento = {}

	def _plantillas_CAD(self):
		"""Obtiene las plantilla {{RCAD}} de la plantilla {{Recordatorio CAD}} y las
		almacena en dos variables para su posterior uso."""
		patron_busqueda = r"\{\{RCAD\|(?P<art>[^|]+?)\|(?P<act>[^|]*?)\|(?P<usr>[^|]*?)\|(?P<obs>[^}]*?)\}\}"
		self.plantillas = re.findall(r"\{\{RCAD\|([^|]+?)(?:\|[^|]+?){0,2}\|[^}]*?\}\}", self._contenido)
		self._info_plantillas = dict(
			[
				(
					pl.group("art"), {
						"act": int(pl.group("act")) if pl.group("act").isdigit() else "",
						"usr": int(pl.group("usr")) if pl.group("usr").isdigit() else 0,
						"obs": pl.group("obs")
					}
				) for pl in re.finditer(patron_busqueda, self._contenido)
			]
		)
		self.recuento["inicial"] = len(self.plantillas)

	def _plantillas_VAD(self):
		"""Obtiene las plantilla {{RVAD}} de la plantilla {{Recordatorio CAD}} y las
		almacena en dos variables para su posterior uso."""
		patron_busqueda = r"\{\{RVAD\|(?P<art>[^|]+?)\|(?P<act>[^|]*?)\|(?P<usr>[^|]*?)\|(?P<obs>[^}]*?)\}\}"
		self.plantillasVAD = re.findall(r"\{\{RVAD\|([^|]+?)(?:\|[^|]+?){0,2}\|[^}]*?\}\}", self._contenido)
		self._info_plantillas.update(
			dict(
				[
					(
						pl.group("art"), {
							"act": int(pl.group("act")) if pl.group("act").isdigit() else "",
							"usr": int(pl.group("usr")) if pl.group("usr").isdigit() else 0,
							"obs": pl.group("obs")
						}
					) for pl in re.finditer(patron_busqueda, self._contenido)
				]
			)
		)
		self.recuento["inicialVAD"] = len(self.plantillasVAD)

	def editar(self):
		"""Actualiza [[Plantilla:Recordatorio CAD]]"""
		output("\n%s\n" % " cambios ".center(30, "="))
		show_diff(self._contenido_original, self._contenido)
		if not args.test and self._contenido_original != self._contenido:
			self._pagina.text = self._contenido
			self._pagina.save(u"Bot: actualizando valores.")

	def principal(self):
		"""Prepara actualización de [[Plantilla:Recordatorio CAD]]"""
		self._plantillas_CAD()
		self._plantillas_VAD()
		antiguos = []
		nuevos = []

		candidatos = [c if isinstance(c, basestring) else c[1] for c in self._candidatos]
		#retira candidatos
		for candidato in self._info_plantillas.keys():
			if candidato not in self.plantillas: continue
			if candidato not in candidatos:
				self.plantillas.remove(candidato)
				antiguos.append(candidato)
		#inserta candidatos nuevos
		for candidato in candidatos:
			if candidato not in self.plantillas: continue
			if not self._info_plantillas.has_key(candidato):
				self.plantillas.append(candidato)
				nuevos.append(candidato)
		#retira votaciones
		for candidato in self._info_plantillas.keys():
			if candidato not in self.plantillasVAD: continue
			if candidato not in candidatos:
				self.plantillasVAD.remove(candidato)
				antiguos.append(candidato)
		#inserta votaciones nuevas
		for candidato in candidatos:
			if candidato not in self.plantillasVAD: continue
			if not self._info_plantillas.has_key(candidato):
				self.plantillasVAD.append(candidato)
				nuevos.append(candidato)
		verbose(
			u">>>>>>>\ncandidatos:%r\nplantillas%r\ninfoplantillas%r\n<<<<<<<" % (
				self._candidatos, self.plantillas, self._info_plantillas
			), 2
		)

		#recorremos las candidaturas actualizando datos
		#e informando de los cambios
		patron_reemplazo = r"\{\{RCAD\|%s\|(?P<act>[^|]*?)\|(?P<usr>[^|]*?)\|(?P<obs>[^}]*?)\}\}"
		patron_supresion = r"\{\{RCAD\|%s.*\}\}[\r\n\s]+"
		cadena_reemplazo = ur"{{RCAD|%s|%s|%s|%s}}"
		cadena_reemplazo_nueva = ur"\1{{RCAD|%s|%s|%s|%s}}\n|}"

		patron_reemplazo_vad = r"\{\{RVAD\|%s\|(?P<act>[^|]*?)\|(?P<usr>[^|]*?)\|(?P<obs>[^}]*?)\}\}"
		patron_supresion_vad = r"\{\{RVAD\|%s.*\}\}[\r\n\s]+"
		cadena_reemplazo_vad = ur"{{RVAD|%s|%s|%s|%s}}"
		cadena_reemplazo_nueva_vad = ur"\1{{RVAD|%s|%s|%s|%s}}\n|}"

		for articulo in candidatos + antiguos:
			verbose(u"examinando [[\3{lightblue}%s\3{default}]]" % articulo)
			if articulo in antiguos:
				verbose(u"\t\3{lightred}retirando la plantilla.\3{default}")
				self._contenido = re.sub(patron_supresion % re.escape(articulo), "", self._contenido)
			else:
				if articulo in self.plantillas:
					#parche temporal bug candidato sin página cadidatura --coet 2014-11-21
					try:
						candidato = Candidato_CAD(articulo)
						participacion = candidato.participacion()
						participantes = candidato.participantes()
						observaciones = candidato.observaciones()
					except NoPage:
						participacion = participantes = observaciones = "sin datos"
					verbose("%s: p1:%r | p2:%r | o:%r" % (candidato._articulo, participacion, participantes, observaciones), 2)
					if articulo not in nuevos:
						archivo = self._info_plantillas[articulo]
						if participacion != archivo['act']:
							notice("actividad", archivo['act'], participacion)
						if participantes != archivo['usr']:
							notice("usuarios", archivo['usr'], participantes)
						if observaciones != archivo['obs']:
							notice("otros", archivo['obs'], observaciones)
						self._contenido = re.sub(
							patron_reemplazo % re.escape(articulo),
							cadena_reemplazo % (articulo, participacion, participantes, observaciones),
							self._contenido
						)
					elif articulo in nuevos:
						verbose(u"\t\3{lightgreen}añadiendo la plantilla.\3{default}")
						self._contenido = re.sub(
							r"(\|-\s+?|\{\{RCAD\|[^}]+?\}\}\s+?)\|}",
							cadena_reemplazo_nueva % (articulo, participacion, participantes, observaciones),
							self._contenido,
							flags = re.S
						)
				elif articulo in self.plantillasVAD:
					#parche temporal bug candidato sin página cadidatura --coet 2014-11-21
					try:
						candidato = Candidato_VAD(articulo)
						participacion = candidato.participacion()
						participantes = candidato.participantes()
						observaciones = candidato.observaciones()
					except NoPage:
						participacion = participantes = observaciones = "sin datos"
					verbose("%s: p1:%r | p2:%r | o:%r" % (candidato._articulo, participacion, participantes, observaciones), 2)
					if articulo not in nuevos:
						archivo = self._info_plantillas[articulo]
						if participacion != archivo['act']:
							notice("actividad", archivo['act'], participacion)
						if participantes != archivo['usr']:
							notice("usuarios", archivo['usr'], participantes)
						if observaciones != archivo['obs']:
							notice("otros", archivo['obs'], observaciones)
						self._contenido = re.sub(
							patron_reemplazo_vad % re.escape(articulo),
							cadena_reemplazo_vad % (articulo, participacion, participantes, observaciones),
							self._contenido
						)
					elif articulo in nuevos:
						verbose(u"\t\3{lightgreen}añadiendo la plantilla.\3{default}")
						self._contenido = re.sub(
							r"(\|-\s+?|\{\{RVAD\|[^}]+?\}\}\s+?)\|}",
							cadena_reemplazo_nueva_vad % (articulo, participacion, participantes, observaciones),
							self._contenido,
							flags = re.S
						)

		self._contenido = re.sub("Actualizado por .*\n", "Actualizado por ~~~~\n", self._contenido)
		self.recuento.update({"nuevos": len(nuevos), "antiguos": len(antiguos), "final": len(self.plantillas), "finalVAD": len(self.plantillasVAD)})

	def actualizar_contador_cad(self):
		"""Actualiza [[Wikipedia:Candidatos a artículos destacados/Número]]"""
		verbose(
			u"plantillas añadidas: %i, plantillas retiradas: %i\nantes: %i | después: %i" % (
				self.recuento["nuevos"],
				self.recuento["antiguos"],
				self.recuento["inicial"],
				self.recuento['final']
			)
		)
		texto = u"<noinclude>Pon aquí el número de "
		texto += u"[[Wikipedia:Candidatos a artículos destacados|candidaturas para artículo destacado]] "
		texto += u"que están actualmente en curso: </noinclude>%i" % self.recuento['final']
		pagina = Page(site, u"Wikipedia:Candidatos a artículos destacados/Número")
		if not args.test and texto != pagina.text:
			pagina.text = texto
			pagina.save(u"Bot: actualizando el contador: %i cand." % self.recuento['final'])

	def actualizar_contador_vad(self):
		"""Actualiza [[Wikipedia:Candidatos a artículos destacados/NúmeroVAD]]"""
		verbose(
			u"plantillas añadidas: %i, plantillas retiradas: %i\nantes: %i | después: %i" % (
				self.recuento["nuevos"],
				self.recuento["antiguos"],
				self.recuento["inicial"],
				self.recuento['finalVAD']
			)
		)
		texto = u"<noinclude>Pon aquí el número de "
		texto += u"[[Wikipedia:Candidatos a artículos destacados|candidaturas para artículo destacado]] "
		texto += u"que están actualmente en curso: </noinclude>%i" % self.recuento['finalVAD']
		pagina = Page(site, u"Wikipedia:Candidatos a artículos destacados/NúmeroVAD")
		if not args.test and texto != pagina.text:
			pagina.text = texto
			pagina.save(u"Bot: actualizando el contador: %i cand." % self.recuento['finalVAD'])

	def inicio(self):
		self.principal()
		self.editar()
		self.actualizar_contador_cad()
		self.actualizar_contador_vad()

def principal():
	recordatorio.inicio()
	candidatos.inicio()

def parametros():
	#argumentos de entrada del usuario
	args = lambda: x
	args.recordatorio = args.candidatos = args.test = False
	args.alert=1
	for arg in handle_args():
		if ":" in arg:
			key, value = arg.split(":", 1)
		if arg in ("-t", "--test"):
			args.test = True
			output("Modo \3{lightpurple}test\3{default} activado, no se editará.")
		elif arg in ("-c", "--candidatos"):
			args.candidatos = True
		elif arg in ("-r", "--recordatorio"):
			args.recordatorio = True
		elif arg in ("-a", "--alert"):
			args.alert = int(value)
	return args

if __name__ == '__main__':
	#Parámetros de funcionamiento.
	"""
	El parámetro -c o --candidatos actualizará los datos de las tablas de [[Wikipedia:Candidatos a artículos destacados]].
	El parámetro -r o --recordatorio actualizará [[Plantilla:Recordatorio CAD]] y [[Wikipedia:Candidatos a artículos destacados/Número]].
	La omisión de los dos hará la actualización de las tres tareas.
	El parámetro -t o --test impide la edición.
	"""
	#argumentos de entrada del usuario
	args = parametros()

	#Funciones globales.
	verbose = lambda msg, alert=args.alert: alert <= args.alert and output(msg)
	notice = lambda x, y, z: verbose(
		"\t%s de \3{lightpurple}%s\3{default} a \3{lightyellow}%s\3{default}." % (x, y, z)
	)
	timestamp = lambda x: datetime.strptime(x, "%Y-%m-%dT%H:%M:%SZ")
	capitalize = lambda x: isinstance(x, basestring) and len(x)>0 and u"%s%s" % (x[0].upper(), x[1:]) or x

	#Variables globales.
	site = Site("es", user="TronaBot")

	#¡Empezamos!
	verbose(
		"\n\n[{:%H:%M:%S}] ¡Comenzamos! rec:{a.recordatorio} cand:a.candidatos test:{a.test}" . format(
			datetime.now(),
			a=args
		)
	)
	if args.candidatos:
		candidatos = Candidatos()
		candidatos.inicio()
	elif args.recordatorio:
		candidatos = Candidatos()
		recordatorio = Recordatorio()
		recordatorio.inicio()
	else:
		candidatos = Candidatos()
		recordatorio = Recordatorio()
		principal()