agenda-libre-ruby/public/adl-submit.py

402 lines
15 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2005 Thomas Petazzoni <thomas.petazzoni@enix.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import xml.dom.minidom
import getopt
import sys
import pycurl
import io
import re
import time
import locale
from urllib.parse import urlencode
baseUrl = "https://www.agendadulibre.org/"
#baseUrl = "http://localhost:3000/"
locale.setlocale(locale.LC_ALL, ('fr_FR', 'utf-8'))
eventFields = [ "title", "start-date", "end-date", "start-hour",
"end-hour", "description", "place_name", "address", "city",
"region", "url", "contact", "submitter", "tags" ]
regions = {
'Alsace-Champagne-Ardenne-Lorraine' : 1,
'Aquitaine-Limousin-Poitou-Charentes': 2,
'Auvergne-Rhône-Alpes' : 3,
'Normandie' : 4,
'Bourgogne-Franche-Comté' : 5,
'Bretagne' : 6,
'Centre-Val de Loire' : 7,
'Corse' : 9,
'Île-de-France' : 12,
'Languedoc-Roussillon-Midi-Pyrénées' : 13,
'Hauts-de-France' : 17,
'Provence-Alpes-Côte-d\'Azur' : 21,
'Pays de la Loire' : 18,
'Guadeloupe' : 23,
'Guyane' : 24,
'Martinique' : 25,
'La Réunion' : 26,
'Autre pays' : 27,
'Mayotte' : 28,
'Collectivités d\'outre-mer' : 29,
'Collectivité sui generis' : 30
}
def Usage():
print("""Soumettre un évènement dans l'Agenda du Libre
Options:
--file event.xml Fichier XML décrivant l'évènement.
--test-output test.html Fichier de sortie HTML de test
--start-date YYYY-MM-DD Date de début de l'évènement.
--end-date YYYY-MM-DD Date de fin de l'évènement.
--start-hour HH:MM Heure de début de l'évènement.
--end-hour HH:MM Heure de fin de l'évènement.
--title chaine Titre de l'évènement.
--description chaine-html Description de l'évènement.
--place_name chaine Nom du lieu (pas utilisé par OSM)
--address chaine Position de l'événement (utile pour une carte OSM).
--city chaine Ville de l'évènement.
--region chaine Région de l'évènement.
--url chaine URL décrivant l'évènement.
--contact chaine E-mail de contact.
--tags chaine Liste des tags.
Exemple de fichier XML:
<?xml version="1.0" encoding="UTF-8"?>
<event>
<title>Permanence Logiciels Libres</title>
<start-hour>18:00</start-hour>
<end-hour>21:00</end-hour>
<description><![CDATA[
<p><a href="http://www.gulliver.eu.org">Gulliver</a> organise chaque
semaine une permanence <i>Logiciels Libres</i> ouverte à tous,
membre de l'association ou non.</p>
<p>Durant cette permanence, vous pourrez trouver des réponses aux
questions que vous vous posez au sujet du Logiciel Libre, ainsi que
de l'aide pour résoudre vos problèmes d'installation, de
configuration et d'utilisation de Logiciels Libres. N'hésitez pas
à apporter votre ordinateur, afin que les autres participants
puissent vous aider.</p>
<p>Une connexion Internet est disponible sur place, ainsi que les
mises à jour pour les distributions GNU/Linux les plus
courantes.</p>
<p>Cette permanence a lieu à la <a
href=\"http://www.grand-cordel.com/\">MJC du Grand Cordel</a>, 18
rue des Plantes à Rennes.</p>
]]></description>
<place_name>Cabane du pêcheur</place_name>
<address>Place de la république</address>
<city>Rennes</city>
<region>Bretagne</region>
<url>http://www.gulliver.eu.org</url>
<contact>contact@gulliver.eu.org</contact>
<tags>gulliver permanence</tags>
</event>
Valeurs des champs:
Le fichier XML peut contenir des champs dont le nom est semblable
à celui des options, à savoir start-date, end-date,
start-hour, end-hour, title, description, place_name, address, city,
region, url et contact. Si un champ est défini à la fois dans le fichier
XML et sur la ligne de commande, alors c'est la valeur donnée sur la
ligne de commande qui l'emporte. Entre le fichier XML et la ligne de
commande, tous les champs doivent être définis, sinon l'ajout
de l'évènement sera refusé. Le seul champ qui peut être
vide est end-date, auquel cas il sera positionné à la même
valeur que start-date.
Remplacements:
Si la chaîne $month est trouvée dans la description, elle sera
automatiquement remplacée par le nom du mois de la date de début
de l'évènement.
Si la chaîne $date est trouvée dans la description, elle sera
automatiquement remplacée par la date de début de l'évènement
(numéro du jour dans le mois puis nom du mois).
Exemple d'utilisation:
./adl-submit.py --file event.xml --start-date 2005-12-10
Ajoutera l'évènement décrit dans le fichier event.xml
(donné ci-dessous) pour la date du 10 décembre 2005. Puisque
le champ end-date n'est pas spécifié, alors il vaudra la
même chose que start-date, c'est à dire le 10 décembre
2005.
Pour vérifier que le formatage est correct avant l'envoi,
on pourra utiliser:
./adl-submit.py --file event.xml --start-date 2005-12-10
--test-output test.html
et regarder le fichier test.html avec un navigateur Web.
""")
sys.exit (1)
def HandleXmlFile(file, values):
dom = xml.dom.minidom.parse(file)
for node in dom.getElementsByTagName("event")[0].childNodes:
if node.nodeType == node.ELEMENT_NODE:
val = node.childNodes[0]
for field in eventFields:
if node.nodeName == field:
values[field] = val.data
def HandleParamValue(param, val, values):
for field in eventFields:
if param == "--" + field:
values[field] = val
def ParseOptions(options):
getoptOptions = [elt + "=" for elt in eventFields]
getoptOptions.append ("file=")
getoptOptions.append ("help")
getoptOptions.append("test-output=")
eventFieldValues = {}
testOutputFile = ""
try:
opts, args = getopt.getopt(options, "", getoptOptions)
except getopt.GetoptError:
print("Option inconnue.")
Usage()
if opts == []:
Usage()
for param, val in opts:
if param == "--help":
Usage()
if param == "--file":
HandleXmlFile(val, eventFieldValues)
if param == "--test-output":
testOutputFile = val
for param, val in opts:
HandleParamValue (param, val, eventFieldValues)
return (eventFieldValues, testOutputFile)
def getAuthToken(baseUrl):
contents = io.BytesIO()
curl = pycurl.Curl()
try:
curl.setopt(curl.URL, baseUrl)
curl.setopt(curl.WRITEDATA, contents)
curl.setopt(pycurl.COOKIEJAR, '/tmp/cookie.txt')
curl.setopt(pycurl.COOKIEFILE, '/tmp/cookie.txt')
curl.perform()
finally:
curl.close()
contents = contents.getvalue().decode()
m = re.findall(r'(<meta name="csrf-token" content="(.*?)" />)', contents)
return m[0][1]
def SubmitEvent(event, testOutputFile):
if "end-date" not in event and 'start-date' in event:
event ["end-date"] = event ["start-date"]
if "submitter" not in event and 'contact' in event:
event ['submitter'] = event['contact']
for field in eventFields:
if field not in event:
print(("Le champ '%s' n'est pas renseigné") % field)
return
if re.compile(r'^[^\<\>]*$').search (event['title']) is None:
print(("Problème de formatage dans le titre: '%s'. Les tags HTML ne sont pas autorisés.") % event['title'])
return
try:
startDate = time.strptime(event['start-date'], "%Y-%m-%d")
except ValueError:
print(("Problème de formatage dans la date de début: '%s'. Elle doit être de la forme AAAA-MM-JJ") % event['start-date'])
return
try:
endDate = time.strptime(event['end-date'], "%Y-%m-%d")
except ValueError:
print(("Problème de formatage dans la date de fin: '%s'. Elle doit être de la forme AAAA-MM-JJ") % event['end-date'])
return
try:
startHour = time.strptime(event['start-hour'], "%H:%M")
except ValueError:
print(("Problème de formatage dans l'heure de début: '%s'. Elle doit être de la forme: HH:MM") % event['start-hour'])
return
try:
endHour = time.strptime(event['end-hour'], "%H:%M")
except ValueError:
print(("Problème de formatage dans l'heure de fin: '%s'. Elle doit être de la forme HH:MM") % event['start-hour'])
return
for tag in event['tags'].split(' '):
if len(tag) < 3:
print(("Le tag '%s' est trop petit, minimum de 3 caractères") % tag)
return
startDate = (startDate[0], startDate[1], startDate[2], startHour[3],
startHour[4], startDate[5], startDate[6], startDate[7], startDate[8])
endDate = (endDate[0], endDate[1], endDate[2], endHour[3],
endHour[4], endDate[5], endDate[6], endDate[7], endDate[8])
if time.mktime(startDate) <= time.time():
print("ERREUR: La date de début de l'évènement est dans le passé.")
return
if time.mktime(endDate) <= time.time():
print("ERREUR: La date de fin de l'évènement est dans le passé.")
return
if time.mktime(endDate) < time.mktime(startDate):
print("ERREUR: La date de fin de l'évènement est avant la date de début.")
return
if re.compile(r'^[^\<\>]*$').search (event['city']) is None:
print(("ERREUR: Problème de formatage dans le nom de la ville: '%s'. Les tags HTML sont interdits.") % event['city'])
return
if (event['region'] in regions) is False:
print(("ERREUR: La région '%s' n'existe pas.") % event['region'])
print("Les régions existantes sont:")
for name in regions:
print((" - ") + name)
return
if re.compile(r'^http://.*$').search (event['url']) is None and re.compile(r'^https://.*$').search (event['url']) is None:
print(("ERREUR: Problème de formatage dans l'URL: '%s'. Elle doit commencer par http:// ou https://.") % event['url'])
return
if re.compile(r'^([A-Za-z0-9_\.\-]*)@([A-Za-z0-9_\-]*)\.([A-Za-z0-9_\.\-]*)$').search (event['contact']) is None:
print(("ERREUR: Problème de formatage dans l'adresse e-mail.") % event ['contact'])
return
if re.compile(r'^([A-Za-z0-9_\.\-]*)@([A-Za-z0-9_\-]*)\.([A-Za-z0-9_\.\-]*)$').search (event['submitter']) is None:
print(("ERREUR: Problème de formatage dans l'adresse e-mail.") % event ['submitter'])
return
monthstr = time.strftime("%B", startDate)
datestr = time.strftime("%d %B", startDate)
event['description'] = event['description'].replace("$month", monthstr)
event['description'] = event['description'].replace("$date", datestr)
curl = pycurl.Curl()
try:
contents = io.BytesIO()
curl.setopt(curl.WRITEFUNCTION, contents.write)
if testOutputFile:
curl.setopt (curl.URL, baseUrl + 'events/preview')
else:
curl.setopt (curl.URL, baseUrl + 'events')
fields = {
'authenticity_token': str(getAuthToken(baseUrl+'events/new')),
'event[title]': event['title'],
'event[start_time(3i)]': str(startDate[2]),
'event[start_time(2i)]': str(startDate[1]),
'event[start_time(1i)]': str(startDate[0]),
'event[start_time(4i)]': str(startDate[3]),
'event[start_time(5i)]': str(startDate[4]),
'event[end_time(3i)]': str(endDate[2]),
'event[end_time(2i)]': str(endDate[1]),
'event[end_time(1i)]': str(endDate[0]),
'event[end_time(4i)]': str(endHour[3]),
'event[end_time(5i)]': str(endHour[4]),
'event[description]': event['description'],
'event[place_name]': event['place_name'],
'event[address]': event['address'],
'event[city]': event['city'],
'event[region_id]': str(regions[event['region']]),
'event[locality]': str(0),
'event[url]': event['url'],
'event[contact]': event['contact'],
'event[submitter]': event['submitter'],
'event[tag_list]': event['tags']
}
fields = urlencode(fields)
curl.setopt(curl.POSTFIELDS, fields)
curl.setopt(pycurl.COOKIEJAR, '/tmp/cookie.txt')
curl.setopt(pycurl.COOKIEFILE, '/tmp/cookie.txt')
curl.perform()
if testOutputFile:
if curl.getinfo(curl.HTTP_CODE) != 200:
print("Erreur lors de la récupération de la sortie HTML")
sys.exit(0)
fp = open(testOutputFile, "w")
s = contents.getvalue().decode()
s = re.sub(r'<head.*>', r'\g<0><base href="%s">' % baseUrl, s, flags=re.IGNORECASE)
fp.write(s)
fp.close()
else:
if curl.getinfo(curl.HTTP_CODE) != 302:
print("Erreur lors de la soumission de l'évènement")
sys.exit(0)
else:
print("Évènement soumis avec succès. Il sera prochainement validé par un modérateur.")
finally:
curl.close()
def ensure_latest_version_is_used():
# Check that we are running the latest version of the adl-submit
# script
contents = io.BytesIO()
curl = pycurl.Curl()
try:
curl.setopt(curl.WRITEDATA, contents)
curl.setopt (curl.URL, baseUrl + './adl-submit-latest-version')
curl.perform()
if curl.getinfo(curl.HTTP_CODE) == 200:
contents = contents.getvalue().decode()
if float(contents) != float('3.5'):
print("Votre script n'est plus à jour, merci de télécharger la nouvelle version à l'adresse")
print(("%sadl-submit.py") % baseUrl)
sys.exit(1)
finally:
curl.close()
if __name__ == "__main__":
if (len(sys.argv) <= 1) or sys.argv[1] == "--help":
Usage()
(event, testOutputFile) = ParseOptions(sys.argv[1:])
ensure_latest_version_is_used()
SubmitEvent(event, testOutputFile)