Carnet Wiki

Mashup.api

Version 14 — Novembre 2014 RastaPopoulos

La boucle DATA de SPIP 3.0 permet facilement d’aller chercher des données sur le web dans n’importe quel format (ou presque, un format non supporté nativement ne nécessite qu’une fonction de mise en forme pour pouvoir ensuite être lu).

Il devient ainsi facile de présenter et mettre en forme en HTML ces données distantes au sein de n’importe quelle page d’un site.

Cependant, lorsqu’il s’agit ensuite d’agréger de multiples données de sources différentes dans une même liste, cela devient plus compliqué car la boucle DATA ne permet pas de fusionner des sources. (Exemple : on ne peut pas dans une même boucle DATA présenter des vidéos venant de youtube et de dailymotion sur un même thème)

L’idée est de créer une couche intermédiaire de mashup dans un format pivot. Ce format intermédiaire pourrait ensuite être automatiquement transformé en flux RSS ou ATOM ou en d’autres formats JSON, XML, ou même HTML, et/ou être utilisé par syndication dans le site.

Connecteurs Mashup

Un connecteur est un squelette qui lit les données (externes) et les restitue dans le format mashup-data. C’est typiquement, mais pas exclusivement, un squelette qui utilise DATA pour lire la source externe. Le squelette connecteur est rangé dans mashup-connectors/

Format mashup-data

L’idée est d’avoir un format simple a écrire dans le squelette de façon à se concentrer sur l’extraction des données, et ne pas avoir à se battre avec le spécificités techniques du format.
Pour cela YAML paraît tout indiqué. Il est naturellement lisible, et permet de structurer a minima les données sans difficulté (à confirmer à l’usage).
Chaque donnée extraite de la source serait ré-exprimée dans les champs suivants (inspirés de RSS) :

  • id : identifiant unique de la donnée
  • title : champ obligatoire, qui titre la donnée
  • date : date de la donnée
  • author : auteur de la donnée
  • source : url source de la donnée (et non du jeu de données), si celle ci existe
  • url : lien vers lequel on peut renvoyer à partir de la donnée
  • abstract : introduction/résumé de la donnée pour une présentation courte. Contient du HTML
  • content : contenu complet de la donnée. Contient du HTML
  • documents : sous-liste de documents joints décrits par leur url

Les données sont une sous-liste du jeu de données, lui même décrit par les informations :

  • id : identifiant unique du jeu de données
  • start : début des données renvoyées
  • count : nombre de données renvoyées
  • total : nombre total de données disponibles

Exemple de connecteur (tiré de http://spip3.quejai.me/rss-afficher-un-flux-de-photos-de-flickr) :

#HTTP_HEADER{Content-type:text/plain}
--- # RSS to Mashup-data
-
  id: "http://api.flickr.com/services/feeds/photos_public.gne?tags=parisweb2011&lang=fr-fr&format=rss_200"
<B_flickr>
  start: [(#ENV{debutdata,0}|min{#GRAND_TOTAL|moins{#TOTAL_BOUCLE}})]
  count: [(#TOTAL_BOUCLE)]
  total: [(#GRAND_TOTAL)]
  data:
<BOUCLE_flickr(DATA)
       {source rss, http://api.flickr.com/services/feeds/photos_public.gne?tags=parisweb2011&lang=fr-fr&format=rss_200}
			 {pagination #ENV{limit,10} data}
       >
    -
      id: [(#VALEUR{url})]
      title: [(#VALEUR{titre})]
      date: [(#VAL{Y-m-d H:i:s}|date{#VALEUR{date}})]
      author: [(#VALEUR{lesauteurs})]
      source: [(#VALEUR{url})]
      url: [(#VALEUR{url})]
      lang: [(#VALEUR{lang})]
      content: [(#VALEUR{descriptif}|html_in_yaml)]
      documents:
        - [(#VALEUR{enclosures}|extraire_attribut{href})]
      tags: ["[(#VALEUR{tags}|implode{'", "'}|strip_tags)]"]
</BOUCLE_flickr>
	start: 0
  count: 0
  total: [(#GRAND_TOTAL)]
  data: []
<//B_flickr>

On note le filtre |html_in_yaml qui se contente d’indenter le html avec un nombre fixé d’espaces, et de le prefixer d’un « | » permettant ainsi la lecture du YAML :

function html_in_yaml($html,$indent=24){
	$html = "|\n$html";
	$html = str_replace("\r\n","\n",$html);
	$html = str_replace("\r","\n",$html);


$html = str_replace("\n","\n".str_pad("",$indent," "),$html);
	return $html;
}

La sortie YAML générée par ce connecteur est alors :

--- # RSS to Mashup-data
-
  id: "http://api.flickr.com/services/feeds/photos_public.gne?tags=parisweb2011&lang=fr-fr&format=rss_200"


start: 0
  count: 10
  total: 20
  data:


-
      id: http://www.flickr.com/photos/parisweb/6806935280/
      title: 13 octobre
      date: 2012-03-04 20:03:53
      author: parisweb
      source: http://www.flickr.com/photos/parisweb/6806935280/
      url: http://www.flickr.com/photos/parisweb/6806935280/
      lang: 
      content: |
                        <p><a href="http://www.flickr.com/people/parisweb/">parisweb</a> a posté une photo :</p>
                        	
                        <p><a href="http://www.flickr.com/photos/parisweb/6806935280/" title="13 octobre"><img src="http://farm8.staticflickr.com/7038/6806935280_ce69bb8e60_m.jpg" width="240" height="160" alt="13 octobre" /></a></p>
      documents:
        - http://farm8.staticflickr.com/7038/6806935280_ce69bb8e60_b.jpg
      tags: ["parisweb", "thanhnguyen", "parisweb2011"]


-
      id: http://www.flickr.com/photos/parisweb/6953038151/
      title: 13 octobre
      date: 2012-03-04 20:01:10
      author: parisweb
      source: http://www.flickr.com/photos/parisweb/6953038151/
      url: http://www.flickr.com/photos/parisweb/6953038151/
      lang: 
      content: |
                        <p><a href="http://www.flickr.com/people/parisweb/">parisweb</a> a posté une photo :</p>
                        	
                        <p><a href="http://www.flickr.com/photos/parisweb/6953038151/" title="13 octobre"><img src="http://farm8.staticflickr.com/7040/6953038151_74626a07ee_m.jpg" width="240" height="160" alt="13 octobre" /></a></p>
      documents:
        - http://farm8.staticflickr.com/7040/6953038151_74626a07ee_b.jpg
      tags: ["parisweb", "thanhnguyen", "parisweb2011"]
...

On y note le contenu humainement lisible, sans échappement du HTML notamment.

API de sortie

L’API de sortie est une URL de la forme
/mashup.api/nomduconnecteur.format?arg1=value&...
avec :

  • nomduconnecteur : nom du squelette connecteur rangé dans le dossier mashup-connectors/ qui fournit les données dans le format intermédiaire du mashup
  • format : format de sortie des données (rss, atom, json, xml, html, yaml...). Le format de sortie est automatiquement généré à partir du format de mashup
  • arg1=value... : arguments passés au squelette connecteur

Exemples d’utilisations

Un connecteur peut être spécifique à un jeu de données, ou plus générique, utilisant alors les arguments fournis dans l’appel à mashup.api pour déterminer sa source.

Proxy
L’API de mashup peut être tout simplement utilisée comme un proxy qui va unifier et homogénéiser des flux de données externes hétérogènes. Chaque source de données prise en charge par un connecteur est alors disponible dans un des formats au choix pris en charge automatiquement par le plugin, pour n’importe quel usage par un site externe.

Sites syndiqués dans SPIP
Chaque flux de donnée peut être rediffusé naturellement sous n’importe quel format supporté par le plugin. En particulier, la rediffusion au format RSS permet de syndiquer les différentes sources de données dans SPIP, et de les agréger dans le site sous forme d’articles syndiqués.

Un exemple d’utilisation serait d’écrire un connecteur mashup-connectors/videoyoutube.html qui va chercher des vidéos sur un thème donné dans Youtube (comme c’est fait ici par exemple http://spip3.quejai.me/afficher-des-videos-de-youtube), ainsi qu’un autre connecteur analogue mashup-connectors/videodailymotion.html pour aller chercher les vidéos sur le même thème sur DailyMotion.

Ensuite, on peut ajouter les deux sources dans SPIP sous formes de sites syndiqués, par leurs urls respectives /mashup.api/videoyoutube.rss?query=spip et /mashup.api/videodailymotion.rss?query=spip.
Dans le site public, il devient facile, avec une boucle SYNDIC_ARTICLES de présenter les deux sources en une liste unique de vidéos sur le même thème, provenant des deux sources.

Syndication en Articles
Toutefois, pour organiser un site à vocation éditoriale et constitué en partie de contenu propre et en partie de contenus externes syndiqués, il devient vite préférable de syndiquer les contenus externes dans des articles SPIP, qui offrent une plus grande liberté éditoriale, et permettent aussi l’édition manuelle pour corriger, compléter, annoter... un contenu venant d’une source externe. Par ailleurs, cela permet de mettre au même niveau de présentation, dans le site public, les contenus d’origine externe et les contenus propres du site.

Pour ce faire, le plugin ajouterait également une interface au backoffice pour administrer et syndiquer directement ses sources de données et construire son Mashup sans passer par les sites syndiqués.

Une source de donnée pourrait ainsi être utilisée pour créer automatiquement des articles à partir du format mashup-data avec les options de suivantes configurables source par source :

  • rubrique dans laquelle les articles sont syndiqués
  • création automatique de sous-rubriques en fonction de la date (aaaa ou aaaa/mm ou aaaa/mm/jj)
  • statut de l’article lors de la syndication : publié/proposé/en rédaction

Le remplissage de l’article se ferait par une règle générique de conversion depuis mashup-data vers les champs de l’article, sauf si la donnée fournie par le connecteur contient un item « article » qui explicite le remplissage de l’article (au format html ou raccourcis SPIP)

-article :
 - titre : "..."
 - descriptif : "..."
 - chapo : "..."
...

Ainsi, pour chaque source, il suffirait de réaliser son connecteur, puis d’aller dans l’interface et de déclarer une nouvelle source de données s’appuyant sur ce connecteur.

On peut ainsi construire un site complet de Mashup en y mixant du contenu rédactionnel propre ainsi que du contenu issu de différentes sources externes et hétérogènes.

En particulier, cela vaut pour les flux RSS : un connecteur mashup-connectors/fromrss.html prendrait un flux RSS fourni par l’argument « url » et le mettrait au format data-mashup.
Il peut ensuite être syndiqué dans des articles SPIP, et cela permet de régler notamment le besoin fonctionnel de syndication d’un flux RSS en articles, sans stocker en doublon les données dans des articles syndiqués (ce que font déjà différemment au moins 3 plugins comme par exemple http://plugins.spip.net/rssarticle.html).

Sécurité

Le connecteur peut utiliser les arguments fournis dans l’url de l’API, mais attention, car cela peut poser des problèmes de sécurité. Par exemple le connecteur mashup-connectors/fromrss.html, décrit ci-dessus qui utilise un argument url pour aller chercher n’importe quelle source RSS, permet à quelqu’un de mal intentionné de rediffuser un flux RSS de données malveillantes (ou pirates, illégales...) en usurpant l’identité du site SPIP.

Il faudrait prévoir deux types d’arguments : ceux passés par URL dans l’API, qui ne sont pas sûrs, et doivent être utilisés avec prudence, et ceux passés par le back-office quand on déclare une source de données externe via l’interface.

On peut imaginer que #ENV{safe/...} contiendrait uniquement les arguments passés par déclaration dans le back-office, mais #ENV contiendrait tous les arguments (que ce soit par url de l’API ou par déclaration).

On peut ainsi se référer à #ENV{safe/url} pour utiliser une source externe de confiance dans un connecteur générique. Corolairement, il faudrait qu’une source de données déclarée dans le back-office dispose d’une URL propre pour pouvoir la rediffuser.

Question : est-ce que ça a un sens du coup de permettre de passer des arguments depuis l’url de l’API ? ou doit-on toujours obliger à passer par déclaration dans le back-office dans ce cas ?

Pour info, dans Drupal

Dans Drupal, il y a un module « Feeds » qui gère cette problématique de mélanger des données venant de plusieurs endroits dans un même flux normalisé. Puis ce flux peut être utilisé, pour insertion basique, pour créer du contenu, pour créer des utilisateurs, ou n’importe quoi d’autre.

La page du projet
La genèse du projet dans un article de 2009
Des captures de l’interface avec notamment ce commentaire :

<blockquote class="spip">

Feeds distinguishes between Fetchers that pull or push content into Drupal, parsers that normalize this content into a PHP array and processors that « do stuff » with this normalized data.

</blockquote>