import urllib.request import json import logging import requests import argparse import datetime import tqdm import csv logger = logging.getLogger('wiki.importer') default_categories = [ 'Person', 'Institution', 'Technology', 'Deployments', 'Dataset', 'City', 'Country', ] parser = argparse.ArgumentParser( description='Turn wiki into nodes & links, usable by d3-force.') parser.add_argument('--categories', metavar='categories', default=default_categories, nargs='+', help='Categories') parser.add_argument('--url', default="https://www.securityvision.io/wiki/api.php", help='Wiki API URL') parser.add_argument('--output', default="semantic_data.json", help='Output JSON file') parser.add_argument('--credentials', default="no_credentials.json", help="JSON file containing the Bot's credentials") parser.add_argument('--csv', default="test.csv", help="CVS file to import") parser.add_argument('--citeref', default="technopoliceFrMarch2020", help="Bibliography key for imported items") args = parser.parse_args() with open(args.credentials) as fp: credentials = json.load(fp) username = credentials['user'] password = credentials['password'] def get_session(): S = requests.Session() URL = args.url # Retrieve login token first PARAMS_0 = { 'action': "query", 'meta': "tokens", 'type': "login", 'format': "json" } R = S.get(url=URL, params=PARAMS_0) DATA = R.json() logger.debug(DATA) LOGIN_TOKEN = DATA['query']['tokens']['logintoken'] logger.debug(LOGIN_TOKEN) # Send a post request to login. Using the main account for login is not # supported. Obtain credentials via Special:BotPasswords # (https://www.mediawiki.org/wiki/Special:BotPasswords) for lgname & lgpassword PARAMS_1 = { 'action': "login", 'lgname': username, 'lgpassword': password, 'lgtoken': LOGIN_TOKEN, 'format': "json" } R = S.post(URL, data=PARAMS_1) DATA = R.json() logger.debug(DATA) if DATA['login']['result'] != 'Success': raise Exception("Failed logging in") return S # Map columns # split on | [ 'Title' # Title, 'Date added' # -, 'Template' # Category (map name & fields), 'Type de document administratif', # Documents administratifs 'Date', # Lois ou règlements & Documents administratifs 'Produit par', # Documents administratifs & Lois ou règlements 'Titre complet', # Lois ou règlements & Documents administratifs 'Projet(s) lié(s)', # Documents/Lois/Contentieux # "Financement BPI Safe City|Convention d'expérimentation Safe City Nice|Réponse Etablissement Paris La Déf. Projet SafeCity" 'Document(s) lié(s)', 'Origine du document', 'Description', 'Sujet(s)', # set list of items 'URL', 'Nom complet', # acteurs: "Type d'acteur", # Acteurs 'Compétences', # Acteurs: "Police et justice|Technologies, innovation, R&D" 'Adresse', # Acteurs 'Fait partie de', # Acteurs: Page 'Image', 'Application(s)', # Projet: Set list of items 'Enjeu(x) prioritaire(s)', 'Durée du projet', # Project: date range, eg me/01/2020~sa/12/2022 -> Jan 1, 2020 ~ Dec 31, 2022 'Description du projet', # Project: (body) text "Coût du projet (en millions d'euros)", # Projet: number * 1000.000 (project) 'Commanditaire(s)', # Projet: Page 'Financements publics', # Projet: Page 'Entreprise(s) prestataire(s)', # Projet Page 'Type de document', 'Type de loi ou règlement', # Lois ou règlements "Date d'adoption", 'Domaine(s)', "Période d'applicabilité", 'Dernière modification', 'Juridiction', 'Geolocation', # geolocation (projet, Acteurs) 'Documents', 'Attachments', 'Published' ] [ 'Title', # Title, 'Date added',# -, 'Template' # Category (map name & fields), 'Application(s)', # Projet: Set list of items 'Enjeu(x) prioritaire(s)', # Projet: Set list of items 'Durée du projet', # Project: date range, eg me/01/2020~sa/12/2022 -> Jan 1, 2020 ~ Dec 31, 2022 'Description du projet', # Project: (body) text "Coût du projet (en millions d'euros)", # Projet: number * 1000.000 (project) 'Commanditaire(s)', # Projet: Page 'Financements publics', # Project: Page 'Entreprise(s) prestataire(s)', # Project: Page 'Document(s) lié(s)', # - 'URL', # Url 'Nom complet', # - "Type d'acteur", # Acteur: set list 'Compétences', # Acteurs: "Police et justice|Technologies, innovation, R&D" 'Adresse', # Acteurs text 'Fait partie de', # Acteurs: Page 'Image', 'Description', # Body text 'Geolocation', # geolocation (projet, Acteurs) 'Documents', 'Attachments', 'Published' ] def mapEntry(entry) -> dict: # 'URL', # Url (split by |) # 'Description', # Body text # 'Geolocation', # convert to City if entry['Template'] == 'Projets': return mapDeployment(entry) elif entry['Template'] == 'Acteurs': return mapInstitution(entry) else: logger.critical(f"Invalid category/Template for entry: {entry['Template']}") def parseStrings(*input): items = [] for i in input: items.extend(i.split('|')) return items def parseGeo(loc): if not len(loc): return '' return '°,'.join(loc.split('|')) + '°' def parseDate(d): # date is in odd format, so we skip the day (which is DoW instead of DoM) # me/01/2020~sa/12/2022 -> Jan 1, 2020 ~ Dec 31, 2022 if '/' in d: parts = d.split('/') return f"{parts[1]}/{parts[2]}" return d def mapDeployment(entry): global args return { 'title': entry['Title'], '@type': 'Deployments', 'properties': { "Keywords": parseStrings( entry['Application(s)'], entry['Enjeu(x) prioritaire(s)'], ), "Managed by": parseStrings(entry['Commanditaire(s)']), "Deployment Start Date": parseDate(entry['Durée du projet'].split('~')[0]), "Deployment End Date": parseDate(entry['Durée du projet'].split('~')[1]) if '~' in entry['Durée du projet'] else '', }, "additionalProperties": { "Budget": int(entry["Coût du projet (en millions d'euros)"]) * 1000000 if entry["Coût du projet (en millions d'euros)"] else None, "Funded by": parseStrings(entry['Financements publics']), "Provided by": parseStrings(entry['Entreprise(s) prestataire(s)']), "URL": entry['URL'], "Geolocation": parseGeo(entry['Geolocation']), 'CiteRef': args.citeref, }, "body": [entry['Description du projet'], entry['Description']] } # Deployments # 'Application(s)': "Keywords": Capteurs audios, Vidéosurveillance automatisée, Fichiers, Statistiques Big Data,Identification biométrique, profilage, # 'Enjeu(x) prioritaire(s)', # Projet: Set list of items (Keywords): Technologies, innovation, R&D, Transport, Éducation, Police et justice, # 'Durée du projet', # Deployment_Start_Date Deployment_End_Date Project: date range, eg me/01/2020~sa/12/2022 -> Jan 1, 2020 ~ Dec 31, 2022 # 'Description du projet', # Project: (body) text # "Coût du projet (en millions d'euros)", # Projet: number * 1000.000 (project) # 'Commanditaire(s)', # Projet: Page # 'Financements publics', # Project: Page # 'Entreprise(s) prestataire(s)', # Project: Page pass def parseType(type): typemap = { 'Entreprise': 'Company', 'Collectivité territoriale': 'Local Government', 'Association': 'NGO', 'Syndicat': 'Labor union', 'Institution ou organisme public': 'Government', 'Juridiction ou autorité de régulation': 'Government', 'Juridiction': 'Government', } return typemap[type] def mapInstitution(entry): global args return { 'title': entry['Title'], '@type': 'Institution', 'properties': { "Keywords": parseStrings( entry['Compétences'] ), "Institution Type": parseType(entry["Type d'acteur"]), "Deployment Start Date": parseDate(entry['Durée du projet'].split('~')[0]), "Deployment End Date": parseDate(entry['Durée du projet'].split('~')[1]) if '~' in entry['Durée du projet'] else '', 'URL': entry['URL'], 'Address': entry['Adresse'], 'Related Institutions': parseStrings(entry['Fait partie de']) }, "additionalProperties": { "Geolocation": parseGeo(entry['Geolocation']), 'CiteRef': args.citeref, }, "body": [entry['Description']] } # "Type d'acteur", # Institution_Type: set list: Entreprise, Collectivité territoriale, Association, Syndicat, Institution ou organisme public, Juridiction ou autorité de régulation, Juridiction # 'Compétences', # Keywords: Droits fondamentaux, Éducation, "Police et justice|Technologies, innovation, R&D" # 'Adresse', # Address text # 'Fait partie de', # Acteurs: Page link in Body: [[Collaborates With::NAME]] def renderPage(data): global args page = f"{{{{{data['@type']}" for key, value in data['properties'].items(): page += f"\n|{key}=" + (', '.join(value) if isinstance(value, list) else value) page += "}}\n\n" for b in data['body']: if b and len(b): page += f"
{b} [[CiteRef::{args.citeref}]]
\n\n" if len(data['additionalProperties']): page += "=== Additional properties ===\n\n" for key, value in data['additionalProperties'].items(): if not isinstance(value, list): value = [value] for v in value: if v: page += f"* {key} [[{key}::{v}]]\n" return page def saveIfNotExists(data, page, session, token): # https://en.wikipedia.org/w/api.php?action=query&prop=info&titles=New%20York%20Yankeesdfsdf # baseurl = f"{args.url}?action=query&list=categorymembers&cmtitle=Category:{category}&format=json" params = { 'action': 'edit', 'createonly': '1', 'title': data['title'], 'contentformat': 'text/x-wiki', 'text': page, 'format': 'json', 'token': token, } logger.debug(args.url, params) logger.warning(f"Creating {data['title']}") response = session.post(args.url, data=params) resp = response.json() if 'warnings' in resp: logger.warning(resp) logger.debug(resp) # print(responseData) def getEditToken(session): params = { 'action': "query", 'meta': "tokens", 'type': "csrf", 'format': "json" } R = session.get(args.url, params=params) DATA = R.json() logger.debug(DATA) return DATA['query']['tokens']['csrftoken'] if __name__ == "__main__": logger.setLevel(logging.DEBUG) session = get_session() token = getEditToken(session) i = 0 with open(args.csv, newline='') as csvfile: csvreader = csv.DictReader(csvfile, delimiter=',') for row in csvreader: data = mapEntry(row) page = renderPage(data) saveIfNotExists(data, page, session, token) i+= 1 # if i > 5: # break