FormatBot
Résumé
Scanne les différents articles et met à un format standard (AAAA.MM.JJ ou AAAA.MM ou AAAA) toutes les dates contenues dans un hyper-mot.
Description technique
Description
Pour chaque page récente et valide, FormatBot effectue sur les hypermots qu'il interprète comme une date les modifications suivantes :
- Il détecte si la date contient une année, ou une année et un mois, ou une année et un mois et un jour.
- Pour chacun de ces trois cas, il vérifie que la date est dans le bon ordre et qu'elle est bien séparée par des points. Si ce n'est pas le cas, elle réécrit la date dans le bon ordre et avec les bons séparateurs.
- Lorsque l'hypermot a une distinction entre le lien vers une autre page et l'affichage de cet hypermot (l'hypermot est affiché sous la forme \[ lien | affichage\], il ne modifie que la partie affichage de l'hypermot pour ne pas casser le lien.
Les hypermots sont reconnus comme une date lorsque :
- ils sont dans un des formats suivants :
- Année.Mois.Jour,
- Jour.Mois.Année,
- Année.Mois,
- Mois.Année,
- Année.
- L'année doit avoir 3 ou 4 chiffres, et les mois et les jours doivent avoir 2 chiffres au plus.
- Chaque séparateur est compris est dans une liste prédéfinie
Exemple 1
Prenons l'exemple suivant :
- 1874/10/30 / Woodstock, Oxfordshire. Naissance de Winston Churchill
Le bot va le lire et corriger la date de la manière suivante :
- 1874.10.30 / Woodstock, Oxfordshire. Naissance de Winston Churchill
Exemple 2
Si la date en question a une redirection différente de celle affichée, la redirection n'est pas modifiée tandis que la date inscrite le sera.
Code wiki
*[[1874-10-30|1874/10/30]] / [[Woodstock, Oxfordshire]]. [[Naissance]] de '''Winston Churchill'''
Code wiki corrigé par le bot
*[[1874-10-30|1874.10.30]] / [[Woodstock, Oxfordshire]]. [[Naissance]] de '''Winston Churchill'''
Donc si l'on appuie sur le lien on sera redirigé vers la date 1874-10-30 même si le format de la date n'est pas affiché de la sorte.
Performance
FormatBot est capable de détecter si un hypermot peut être interprété comme une date, selon plusieurs critères et la réécrit selon un format standard si le format n'est pas correct. Notre bot comporte des limites. En effet, les critères de détection des dates ne permettent pas de détecter toutes les dates qui seraient écrites dans un mauvais format suivant sous quelle elle est écrite. Au niveau des permutations entre année, mois et jour, le bot interprète les deux formats suivants comme étant des dates: "AAAA.MM.JJ" et "JJ.MM.AAAA". Toutes les autres permutations ne sont pas interprétées comme des dates et ne seront pas corrigées. C'est notre choix de ne pas corriger les autres permutations comme par exemple "JJ.AAAA.MM", car on ne peut pas séparer le mois du jour dans 100 % des cas (mois et le jour sont inférieurs à 12). Une autre correction que notre bot effectue concerne les caractères de séparation entre année mois et jour. Nous avons créée un liste des caractères acceptés. Si un autre caractère de séparation est utilisé le bot n'interprète pas l'hypermot concerné comme une date. On pourra réaliser une liste la plus longue possible de caractères acceptées, en tenant compte des espaces pour obtenir le plus grand nombre de détections possible, mais on ne peut pas garantir que tous les formats seront détectés.
Il faut ajouter que la modification du format de la date est réalisée en modifiant le lien correspondant si l'hypermot ne contient pas une barre verticale séparant le lien du format d'affichage de l'hypermot. Si une barre verticale est détectée dans l'hypermot, seulement la partie correspondant au format sera modifié, ce qui préserve le lien.
La détection des dates ne se fait que dans les hypermots pour éviter d'endommager les pages contenant des chiffres dans un paragraphe qui pourrait être interprété comme une date par notre bot mais qui ne devrait pas l'être. Par exemple: "Il a été versé 1955,02 CHF sur son compte" serait interprété comme une date et le format serait changé alors que ne devrait pas être le cas.
Ce bot a été testé sur une cinquantaine de pages il est fonctionnel sur la plupart des pages. Cependant, les modifications sur les pages se font rares car le format de base a très souvent été respecté par la plupart des contributeurs. De plus, il ne devrait pas y avoir de grandes interactions avec d'autres bots si ce n'est ChronoBot qui utilise l'hyperlien de la date mais comme décrit précédemment ce bot fait attention à ne pas détruire un lien en changeant la date et donc éviter le plus possible les interactions avec Chronobot.
Code
# -*- coding: utf-8 -*- import urllib import requests from bs4 import BeautifulSoup user="formatbot" passw='accjjlms' baseurl='http://wikipast.epfl.ch/wikipast/' summary='Wikipastbot update' protected_logins=["Frederickaplan","Maud","Vbuntinx","Testbot","IB","SourceBot","Formatbot","PageUpdaterBot","Orthobot","BioPathBot","ChronoBOT","Amonbaro","AntoineL","AntoniasBanderos","Arnau","Arnaudpannatier","Aureliver","Brunowicht","Burgerpop","Cedricviaccoz","Christophe","Claudioloureiro","Ghislain","Gregoire3245","Hirtg","Houssm","Icebaker","JenniCin","JiggyQ","JulienB","Kl","Kperrard","Leandro Kieliger","Marcus","Martin","MatteoGiorla","Mireille","Mj2905","Musluoglucem","Nacho","Nameless","Nawel","O'showa","PA","Qantik","QuentinB","Raphael.barman","Roblan11","Romain Fournier","Sbaaa","Snus","Sonia","Tboyer","Thierry","Titi","Vlaedr","Wanda"] depuis_date='2017-05-02T16:00:00Z' liste_pages=[] for user1 in protected_logins: result=requests.post(baseurl+'api.php?action=query&list=usercontribs&ucuser='+user1+'&format=xml&ucend='+depuis_date) soup=BeautifulSoup(result.content,'lxml') for primitive in soup.usercontribs.findAll('item'): liste_pages.append(primitive['title']) #print(primitive['title']) liste_pages=list(set(liste_pages)) names=liste_pages # Login request payload={'action':'query','format':'json','utf8':'','meta':'tokens','type':'login'} r1=requests.post(baseurl + 'api.php', data=payload) #login confirm login_token=r1.json()['query']['tokens']['logintoken'] payload={'action':'login','format':'json','utf8':'','lgname':user,'lgpassword':passw,'lgtoken':login_token} r2=requests.post(baseurl + 'api.php', data=payload, cookies=r1.cookies) #get edit token2 params3='?format=json&action=query&meta=tokens&continue=' r3=requests.get(baseurl + 'api.php' + params3, cookies=r2.cookies) edit_token=r3.json()['query']['tokens']['csrftoken'] edit_cookie=r2.cookies.copy() edit_cookie.update(r3.cookies) #fonction qui détecte le début d'une ligne et retourne l'indice du premier caractere apres '*' def line_start_detect(code): line_index_lst=[] for i in range(len(code)): if (code[i]=='\n'): line_index_lst.append(i+2) line_index_lst.append(len(code)) return line_index_lst def hypertext(code, line_index1, line_index2): hypermot_lst=[] for i in range(line_index1, line_index2): if (code[i]== '[' and code[i+1]=='['): j=0 while code[i+j]!=']' and code[i+j+1]!= ']': j+=1 hypermot='' for k in range(i+2,i+j+1): hypermot+=code[k] hypermot_lst.append(hypermot) j=0 i=i+j+2 return hypermot_lst def is_number(char): if(char=='0' or char=='1' or char=='2' or char=='3' or char=='4' or char=='5' or char=='6' or char=='7' or char=='8' or char=='9'): return True else : return False #decode l'hypermot #retourne s'il s'agit d'une date, si le format est correct, et le vecteur de date #sortie : [is_date, (bool) est-ce que cet hypermot peut etre vu comme une date ? # date_format_correct, (bool) le format de date est-il respecte ? # date_format, (int) 1:aaaa 2:aaaa.mm 3:aaaa.mm.jj # ['aaaa','mm','jj'] ] vecteur de la date interpretee #Si is_date est False, le vecteur de date et data_format_correct sont incorrects def date_decode(hypermot): date_format_correct=True is_date=True date_format=0; accepted_separator = ['.','-','. ',' .',' . ',',','/','\ ','_','pizza'] temp_bool_separator_in_lst = False year_index=-1 month_index=-1 day_index=-1 #cree si possible la liste des annee/mois/jours, et les listes des séparateurs number_lst=[''] num_index=0 separator_index_lst=[] separator_lst=[] sep_index=-1 boo=False for i in range(len(hypermot)): if is_number(hypermot[i]): boo=True if boo==False: return [False, False, 0,[]] if not is_number(hypermot[0]): if hypermot[0]== ' ': date_format_correct=False if not is_number(hypermot[1]): #un espace accepté mais pas deux is_date=False return [is_date,date_format_correct,date_format,number_lst] else: is_date=False return [is_date,False,0,[]] else: number_lst[num_index]+=hypermot[0] for i in range(1,len(hypermot)): if not is_number(hypermot[i]): if is_number(hypermot[i-1]): num_index+=1 number_lst.append('') sep_index+=1 separator_lst.append(hypermot[i]) else: separator_lst[sep_index]+=hypermot[i] separator_index_lst.append(i) else: number_lst[num_index]+=hypermot[i] #test de la validite des separateurs for i in range(len(separator_lst)): if separator_lst[i]!='.': date_format_correct=False if len(separator_lst)>2: date_format_correct=False if len(separator_lst)!=3 or separator_lst[2]!=' ': is_date=False for i in range(len(separator_lst)): temp_separator_in_lst = False for j in range(len(accepted_separator)): if separator_lst[i]== accepted_separator[j]: temp_bool_separator_in_lst = True if not temp_bool_separator_in_lst: is_date=False #test de la validite de la date, classification du format de date if len(number_lst)==4: if number_lst[3]=='': date_format=3 elif len(number_lst)<1 or len(number_lst)>3: is_date=False date_format_correct=False else: date_format=len(number_lst) #print(number_lst) for i in range(date_format): #detection de l'annee dans la liste if(len(number_lst[i])==3 or len(number_lst[i])==4): if(year_index !=-1): #s'il y a au moins deux nombres à 3 ou 4 chiffres is_date=False date_format_correct=False year_index=i #print(year_index) if year_index==-1: #si aucune annee n'a ete trouvee is_date=False date_format_correct=False return [is_date,date_format_correct,date_format,number_lst,'pas trouve year'] if date_format==3: if year_index==0: day_index=2 month_index=1 elif year_index==2: day_index=0 month_index=1 date_format_correct = False else: is_date=False date_format_correct=False return [is_date,date_format_correct,date_format,number_lst] if (int(number_lst[month_index])>12 or int(number_lst[month_index])<0 or int(number_lst[day_index])>31 or int(number_lst[day_index])<0): is_date=False date_format_correct=False return [is_date,date_format_correct,date_format,number_lst] if date_format==2: if year_index==0: month_index=1 else: month_index=0 if ( int(number_lst[month_index])>12 or int(number_lst[month_index])<0 ): is_date=False date_format_correct=False return [is_date,date_format_correct,date_format,number_lst] if int(number_lst[year_index])>2050: is_date=False date_format_correct=False if date_format==1: date_final=[ number_lst[year_index] ] if date_format==2: date_final=[number_lst[year_index],number_lst[month_index]] if date_format==3: date_final=[number_lst[year_index],number_lst[month_index], number_lst[day_index]] #rmq: separator_index_lst et separator_lst sont retournes uniquement pour la phase de debugging return [is_date,date_format_correct,date_format,date_final,separator_index_lst,separator_lst] def date_format(decode_lst): date_temp="" for i in range(0,decode_lst[2]) : if i<(decode_lst[2]-1): date_temp+=decode_lst[3][i]+decode_lst[5][i] else : date_temp+=decode_lst[3][i] char_lst=decode_lst[5] for c in char_lst : date_temp=date_temp.replace(c,".") date_final=date_temp.replace(" ","") return date_final ## fonction qui reçoit en argument le code wiki complet la date à modifier et la date formatée ## la fonction ne retourne rien et écrit directement sur la page wiki def wiki_write(code,old_date,new_date,name): new_code = [] new_code = code.replace(old_date,new_date) payload={'action':'edit','assert':'user','format':'json','utf8':'','text':new_code,'summary':summary,'title':name,'token':edit_token} r4=requests.post(baseurl+'api.php',data=payload,cookies=edit_cookie) print(r4.text) print('Modification dans '+name+', '+old_date+' remplacé par '+new_date) return new_code def barre_detect(hypermot): barre=False syntax_part="" for i in range(len(hypermot)) : if hypermot[i]== "|" : barre=True for k in range(i+1, len(hypermot)) : syntax_part+=hypermot[k] if barre==False : syntax_part=hypermot return[barre,syntax_part] for name in names: if(name != "FormatBot"): result=requests.post(baseurl+'api.php?action=query&titles='+name+'&export&exportnowrap') soup=BeautifulSoup(result.text, "lxml") #soup=BeautifulSoup(result.text) code='' print(name) for primitive in soup.findAll("text"): if isinstance(primitive.string,str): code+=primitive.string line_index_lst=line_start_detect(code) a=hypertext(code, line_index_lst[0], line_index_lst[len(line_index_lst)-1]) for i in range(len(a)): barre=barre_detect(a[i]) b=date_decode(barre[1]) if (b[0]==1 and b[1]==0) : date_final=date_format(b) code = wiki_write(code,barre[1],date_final,name)