Pré-requis pour ce tutoriel

  • Des connaissances en Javascript
  • Firefox (une version récente de préférence :) )
  • L’extension GreaseMonkey de Firefox (je vous épargne les détails sur l’installation)

Introduction

Présentation de GreaseMonkey

GreaseMonkey est une extension permettant d’exécuter le Javascript de votre choix au chargement d’une page web afin d’en modifier le comportement, l’aspect ou bien de proposer de nouvelles fonctionnalités. Il existe des scripts GreaseMonkey pour pratiquement tous les sites « célèbres ». Ainsi certains scripts permettent sur Facebook d’afficher l’image d’un contact en plus grand au survol de celle-ci, d’autres suppriment les commentaires (inutiles) sous les vidéos Youtube, de nombreux scripts permettent de tricher sur des sites jeux en ligne en ajoutant du contenu tiré d’une base de données tierce. Les exemples ne manquant pas, je vais m’arrêter ici pour cette liste non exhaustive.

Présentation du script urlExpander

Le but du script que nous allons développer ici va être de « traduire » les urls raccourcies qui foisonnent sur Twitter et consort. De cette manière, on pourra se faire une idée du type de site vers lequel on se dirige et éviter ainsi de se retrouver sur un site de phishing.

Pour faire simple, notre script va uniquement traduire les urls raccourcies venant de bit.ly. Une fois que vous aurez compris le principe, vous pourrez parfaitement étendre le script. L’idée ici est de comprendre GreaseMonkey peut améliorer votre expérience utilisateur sur le web.

Etape 1: Le script qui dit bonjour à tout le monde !

Pour commencer on va faire simple et se contenter d’afficher le classique « Hello World! » que tout le monde connaît si bien. L’objectif ici est de comprendre comment installer un script et comment celui-ci s’exécute.

Pour ce faire vous allez devoir créer un fichier Javascript que vous nommerez « urlExpander.user.js », j’insiste sur la présence du « user » car GreaseMonkey va reconnaître votre script grâce à cela. Ajoutez dans ce fichier le code suivant :

// ==UserScript==
// @name           Epershand urlExpander
// @namespace      http://www.epershand.net
// @description    Traduit les urls raccourcies et affiche le lien entier
// @include        http://twitter.com/*
// ==/UserScript==

alert('Hello World!');

Maintenant que vous avez créé votre fichier JS, faites un glisser/déplacer de ce fichier vers la fenêtre de Firefox, vous devriez voir apparaître le message suivant :

greasemonkey-installation-script

Une fois votre script installé il vous suffit d’aller sur Twitter pour voir s’afficher le résultat de notre premier script :

greasemonkey-hello-world
Si vous avez été attentif vous aurez remarqué que le script contient des commentaires un peu particuliers @include (et son contraire @exclude). Ces deux types de commentaires permettent de définir sur quels sites le script est autorisé à s’exécuter et sur lesquels il n’est pas autorisé. De cette manière vous pourrez cibler Twitter si vous souhaitez que le script s’exécute uniquement dessus.

Etape 2: Lire la documentation de l’API de bit.ly

Pour être en mesure de traduire les urls raccourcies par bit.ly il va nous falloir dialoguer avec l’API de bit.ly, jetons donc ensemble un oeil à la fonction « expand » de l’API de bit.ly (en).

La documentation nous apprend que la fonction expand :

  • est accessible via une adresse de la forme : http://api.bit.ly/expand?version=2.0.1&shortUrl=<url>&login=<login>&apiKey=<api_key>
  • reçoit en paramètre une shortUrl ou bien un hash (le code bizarre qu’on retrouve après l’url raccourcie)
Dans le cadre du développement d’une vraie application utilisant l’API bit.ly je vous conseillerai de créer un compte afin d’obtenir votre clef personnelle vers l’API. Dans notre exemple nous nous contenterons d’utiliser le compte de test que bit.ly utilise dans sa doc, mais chuuut faut pas le dire ;)

Etape 3: Utiliser l’API de bit.ly

Vous pouvez maintenant tester l’API en cliquant sur le lien suivant : http://api.bit.ly/expand?version=2.0.1&shortUrl=http://bit.ly/4s0bFQ&log[...]5f52f629d2d71bf07. Vous devriez obtenir le résultat suivant :

{
	"errorCode": 0, 
	"errorMessage": "", 
	"results": { 
		"4s0bFQ": { 
			"longUrl": "http://www.epershand.net" 
		} 
	}, 
	"statusCode": "OK" 
}

Euh on comprend pas là ! :?

Etant donné que l’API a été développé pour être appelée par des programmes tiers (un peu comme si on appelait un webservice plus light), les données que celle-ci renvoie doivent être dans un format simple et souple. C’est pourquoi le format JSON est utilisé ici. Par ailleurs le format JSON est très facilement manipulable avec du Javascript, et c’est justement ce que nous allons faire!

Certains d’entre vous se demandent sans doute comment nous allons pouvoir appeler l’API avec du Javascript et pouvoir traiter le résultat, et bien dans ce cas l’invite ces personnes à se documenter un peu sur Ajax et l’utilisation de l’objet XmlHttpRequest :)

Pour les autres et bien sachez qu’il suffit de faire une simple requête GET sur l’URL de l’API et d’interpréter les résultats renvoyés par bit.ly avec un eval, et comme nous voulions afficher en clair les urls cibles pour nos utilisateurs, il nous suffira de faire quelques rechercher/remplacer en Javascript dans la page pour obtenir le résultat escompté! Allez en avant! :P

Etape 4: Développement du script

…et là je sens que vous vous demandez si l’on va vraiment développer une fonction pour faire des requêtes Ajax alors qu’il y a déjà un bon paquets de librairies qui le font, et bien je rassure les sceptiques tout de suite, GreaseMonkey intègre fort heureusement un ensemble de fonctions qui permet par exemple d’afficher des messages de logs, de faire des requêtes Ajax, d’enregistrer des données cache, etc.

Etape 4.1: Interroger l’API bit.ly

Pour effectuer une requête Ajax avec GM, il faut utiliser la fonction GM_xmlhttpRequest qui reçoit en paramètre un objet JSON contenant :

  • method : le type de requête HTTP envoyée, GET ou POST
  • url : l’adresse appelée par le script
  • onload : la fonction qui sera exécutée lorsque la requête sera terminée

Nous allons effectuer une première modification sur notre script pour vérifier que l’appel à l’API s’effectue correctement, et pour cela nous utiliserons la fonction GM_log (en) qui permet d’afficher des messages d’erreur dans la console Javascript de votre navigateur, je vous conseille quand même d’utiliser console.log si vous avez Firebug, ca sera nettement plus agréable ;)

var api = 'http://api.bit.ly/expand?version=2.0.1'+
			'&shortUrl=http://bit.ly/4s0bFQ'+
			'&login=bitlyapidemo'+
			'&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07';

GM_xmlhttpRequest({
	method: "GET",
	url: api,
	onload: function(response) {
		GM_log(response); // ou console.log(response);
	}
});

Vous devriez voir s’afficher dans la console Firebug le résultat suivant en cliquant sur « Object » :

greasemonkey-firebug-log

Etape 4.2: Traiter les données de l’API

Maintenant que nous savons comment interroger l’API, il va nous falloir exploiter ces données pour en extraire l’URL complète. Pour pouvoir manipuler la réponse de la requête au format JSON il faut évaluer la chaîne de caractères comme du Javascript grâce à la fonction eval, cette fonction nous renverra donc le résultat sous la forme d’un objet. Une fois cette transformation effectuée, il ne reste plus qu’à parcourir l’objet JSON pour en extraire l’information voulue.

Voici le code qui nous permet d’effectuer ce traitement :

// l'url que nous souhaitons traiter
var shortUrl = 'http://bit.ly/4s0bFQ';

// on extrait le 'hash' de l'url, ici il s'agit de '4s0bFQ'
var hash = shortUrl.substr(shortUrl.lastIndexOf('/')+1);

var api = 'http://api.bit.ly/expand?version=2.0.1'+
			'&shortUrl='+shortUrl+
			'&login=bitlyapidemo'+
			'&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07';

// requête Ajax sur l'API bit.ly
GM_xmlhttpRequest({
	method: "GET",
	url: api,
	onload: function(response) {
		var data = eval('('+response.responseText+')');
		if(data.errorCode == 0) {
			GM_log(data.results[hash].longUrl); // ou console.log(data.results[hash].longUrl);
		}
	}
});

Etape 4.3: Remplacer les URLs raccourcies par les URLs longues

Notre script pour le moment ne fonctionne que sur une seule URL, celle définie en dur, pour qu’il fonctionne sur toutes les urls de la page nous allons le transformer en fonction comme ceci :

// Traduit une URL courte bit.ly
function expand(shortUrl) {
	// on extrait le 'hash' de l'url, ici il s'agit de '4s0bFQ'
	var hash = shortUrl.substr(shortUrl.lastIndexOf('/')+1);

	// url de l'API
	var api = 'http://api.bit.ly/expand?version=2.0.1'+
				'&shortUrl='+shortUrl+
				'&login=bitlyapidemo'+
				'&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07';

	// requête Ajax sur l'API bit.ly
	GM_xmlhttpRequest({
		method: "GET",
		url: api,
		onload: function(response) {
			var data = eval('('+response.responseText+')');
			if(data.errorCode == 0) {
				console.log(data.results[hash].longUrl);
			}
		}
	});
}

expand('http://bit.ly/4s0bFQ');

Arrivé ici, nous avons fait la plus grosse partie du travail, en effet nous avons réussi à interroger l’API de bit.ly pour récupérer l’URL en version longue, pour rendre le script opérationnel il nous faut maintenant remplacer les URLs présentent dans la page par leur équivalent en format long.

Pour cela nous allons parcourir le document HTML à l’aide du DOM à la recherche de toutes les ancres et appeler la fonction sur chaque ancre :

// récupère toutes les ancres de la page
var links = document.getElementsByTagName('a');

// parcourt le tableau des ancres
for(var i=0; i<links.length; i++) {
	var link = links[i];
	
	// appelle la fonction "expand" uniquement 
	// sur les urls ayant pour nom de domaine bit.ly
	if(link.hostname == 'bit.ly') {
		expand(link.href);
	}
}

La touche finale de notre script va être le remplacement de tous les liens bit.ly dans la page, pour cela il va falloir modifier la fonction de callback passée en paramètre à GM_xmlhttpRequest. C’est cette fonction qui aura la charge d’effectuer la modification du texte du lien, nous allons donc devoir améliorer la fonction pour passer en paramètre l’objet DOM de l’ancre et non plus le lien seul, de cette manière la fonction de callback pourra effectuer le remplacement dès que la réponse sera revenue.

// Traduit une URL courte bit.ly
function expand(link) {
	var shortUrl = link.href;
	
	// on extrait le 'hash' de l'url, ici il s'agit de '4s0bFQ'
	var hash = shortUrl.substr(shortUrl.lastIndexOf('/')+1);

	// url de l'API
	var api = 'http://api.bit.ly/expand?version=2.0.1'+
				'&shortUrl='+shortUrl+
				'&login=bitlyapidemo'+
				'&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07';

	// requête Ajax sur l'API bit.ly
	GM_xmlhttpRequest({
		method: "GET",
		url: api,
		onload: function(response) {
			var data = eval('('+response.responseText+')');
			if(data.errorCode == 0) {
				var longUrl = data.results[hash].longUrl;
				with(link) {
					innerHTML = longUrl;
					href = longUrl;
				}
			}
		}
	});
}

// récupère toutes les ancres de la page
var links = document.getElementsByTagName('a');

// parcourt le tableau des ancres
for(var i=0; i<links.length; i++) {
	var link = links[i];
	
	// appelle la fonction "expand" uniquement 
	// sur les urls ayant pour nom de domaine bit.ly
	if(link.hostname == 'bit.ly') {
		expand(link);
	}
}

Pourquoi ne pas utiliser la valeur de retour de la fonction expand? La raison de ce comportement provient du fonctionnement d’Ajax en mode asynchrone ; en effet à partir de l’instant où nous faisons appel à la fonction GM_xmlhttpRequest, notre fonction expand fini de s’exécuter tout de suite après, c’est pour cela qu’il est nécessaire de fournir une fonction en paramètre de GM_xmlhttpRequest. Cette fonction sera exécutée une fois que la requête Ajax sera terminée.

Etape 5: Améliorer le code

Ci-dessous vous trouverez une version améliorée du script proposant un début de modélisation objet en Javascript. De cette manière vous pourrez éventuellement étendre le fonctionnement du script à d’autres types de raccourcisseurs d’URL :up: ou bien ajouter les autres fonctions de l’API. Pourquoi pas une infobulle qui affiche les informations sur le lien à son survol ? ;)

// ==UserScript==
// @name           Epershand urlExpander
// @namespace      http://www.epershand.net
// @description    Traduit les urls raccourcies et affiche le lien entier
// @include        http://twitter.com/*
// ==/UserScript==

// transmet en paramètre à la fonction le document HTML 
// et lance immédiatement l'exécution
(function(d) {
	
	var bitly = {
		expand: function(link) {
			var shortUrl = link.href;
			
			// on extrait le 'hash' de l'url, ici il s'agit de '4s0bFQ'
			var hash = shortUrl.substr(shortUrl.lastIndexOf('/')+1);
		
			// url de l'API
			var api = 'http://api.bit.ly/expand?version=2.0.1'+
						'&shortUrl='+shortUrl+
						'&login=bitlyapidemo'+
						'&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07';
		
			// requête Ajax sur l'API bit.ly
			GM_xmlhttpRequest({
				method: "GET",
				url: api,
				onload: function(response) {
					var data = eval('('+response.responseText+')');
					if(data.errorCode == 0) {
						var longUrl = data.results[hash].longUrl;
						with(link) {
							innerHTML = longUrl;
							href = longUrl;
						}
					}
				}
			});
		}
	};

	// récupère toutes les ancres de la page
	var links = d.getElementsByTagName('a');
	
	// parcourt le tableau des ancres
	for(var i=0; i<links.length; i++) {
		var link = links[i];
		
		// appelle la fonction "expand" uniquement 
		// sur les urls ayant pour nom de domaine bit.ly
		if(link.hostname == 'bit.ly') {
			bitly.expand(link);
		}
	}
		
})(document); 

Pour aller plus loin…

…vous pouvez consulter le très bon manuel Dive into GreaseMonkey (en) et télécharger le code source complet du script GreaseMonkey Epershand urlExpander.