Qu’il s’agisse de la Playstation 5 ou bien des dernières cartes graphiques Nvidia GeForce RTX 30XX vous avez très certainement constaté dernièrement une certaine pénurie.
Contexte actuel oblige, il fallait jouer de chance pour obtenir le Graal avant la rupture. Mais du coup comment faire ?
Certains web marchands proposent un système d’alerte par mail lorsque le produit est de nouveau en stock. En revanche, ce n’est pas toujours très fiable.
Qui n’a jamais rafraîchi la page en spammant la touche F5 en espérant trouver du stock ? Pas besoin de rougir, le problème c’est qu’il faudrait rester scotché derrière l’écran.
C’est là que l’extension Chrome entre en jeu, nous allons pouvoir automatiser l’actualisation d’une page jusqu’à l’obtention du produit.
Mais d’abord c’est quoi une extension Chrome ?
Une extension chrome c’est tout simplement un petit (ou pas) programme qui ajoute des fonctionnalités au navigateur.
En termes de technicité, rien de complexe, on retrouve du JavaScript, HTML et CSS, qui seront zippé en format .CRX par Google.
De nombreuses extensions existent à ce jour et pour n’en citer que 2, les plus connues, on retrouve :
AdBlockPlus, un véritable outils anti-pubs.
Evernote pour la prise de notes et la gestion de l’information
Il est donc tout à fait possible de laisser libre cours à son imagination afin de créer une extension qui vous sera utile aussi bien personnellement que professionnellement.
A titre d’exemple personnel, j’ai créé une extension dans le cadre de ma mission qui me permet de récupérer les informations d’un appel réseau, puis de parser les informations selon un format spécifique et de les copier dans le presse papier. Cette tâche, anciennement chronophage, s’exécute désormais en un clic.
Avant toute chose, commencez par conceptualiser votre extension
Cela peut vous paraître évident mais il est nécessaire de bien imaginer, conceptualiser la manière dont votre plugin va vous aider.
Est-ce que vous voulez appuyer seulement sur un bouton ? Est-ce que vous voulez rajouter des options, des filtres ? Je vous invite même à dessiner votre widget dans un premier temps pour vous aider au niveau du style afin de le personnaliser et d’y mettre votre patte graphique.
Si je vous dis tout ça, c’est parce que l’extension chrome peut afficher une page web sous forme de pop-up, vous n’aurez donc aucune limite à votre imagination. Pour ma part, pour rester dans l’esprit Kaibee et puisqu’elle permet d’acheter à coup sûr un produit, j’a l’ai baptisée BzZBuy et ai choisi ce logo :
Au niveau de l’interface utilisateur, je suis resté basique d’un point de vue fonctionnel et sobre quant au design :
Le besoin est le suivant : pouvoir activer l’extension sur le produit souhaité en indiquant le montant maximum auquel je suis prêt à acheter. Petite sécurité non négligeable pour éviter les mauvaises surprises si un vendeur décide de vendre le produit 3 fois plus cher.
Comment ça marche ?
L’architecture d’une extension dépendra forcément de sa fonctionnalité, mais sachez qu’il est possible d’avoir plusieurs composants :
Voici la composition de l’extension que j’ai développée, et pour l’illustrer je vous renvoie ci-dessous vers un schéma provenant de la documentation de Google.
Le fichier manifest.json fournit à Chrome des informations importantes sur votre extension, comme son nom, sa description et les autorisations dont elle a besoin.
{ "manifest_version": 2, "name": "BzZBuy", "description": "Hey BzZ BzZ help me to buy some stuff", "version": "1.0", "icons":{ "48":"bzZbuy.png" }, "background": { "scripts": ["background.js"] }, "browser_action": { "default_popup": "popup.html", "default_title": "bzZbuy v1.0" }, "content_scripts": [ { "matches": ["https://www.amazon.fr/*"], "js": ["content.js"], "css" : ["css/styles.css"] } ], "permissions": [ "webNavigation", "activeTab", "storage", "tabs" ] }
Le manifest_version doit toujours être 2 , car la version 1 n’est plus prise en charge depuis janvier 2014. Au moment de la rédaction de cet article, le lancement du manifest v3 est imminent.
"content_scripts": [ { "matches": ["https://www.amazon.fr/*"], "js": ["content.js"], "css" : ["css/styles.css"] } ]
Comme le schéma ci-dessus le montre, le content_scripts a accès uniquement à la page du navigateur, il n’est pas connecté aux actions du plugin par exemple. C’est à travers le content script que nous pouvons crawler la page web sur laquelle nous naviguons pour en extraire les informations et effectuer ensuite des actions. En d’autres termes, il pourra extraire une URL de la page actuelle, mais devra remettre cette URL au background pour en faire quelque chose.
Afin de communiquer, nous utiliserons ce que Google appelle le passage de messages, ce qui permet aux scripts d’envoyer et d’écouter des messages. C’est le seul moyen pour les content scripts, le background, et le bouton de l’extension d’interagir.
La clé “matches” vous permet de définir le champ d’activation du script. En l’occurrence, je veux que le script ne soit exécuté que sur Amazon.fr.
"background": { "scripts": ["background.js"] }
Le fichier background.js est le gestionnaire d’événements de l’extension. Il contient des écouteurs pour les événements du navigateur. Fonctionnellement parlant, ce dernier a accès à toutes les API Chrome mais ne peut en revanche accéder à la page actuelle. Nous allons nous en servir comme passerelle pour effectuer nos actions.
Au niveau de l’interface utilisateur on retrouve le fichier popup.html combiné à une logique dans le fichier popup.js. Pour rappel, vous êtes libre au niveau du style mais dans l’idéal : Keep It Simple.
"permissions": [ "webNavigation", "activeTab", "storage", "tabs" ] }
Pour utiliser la plupart des API chrome, vous devrez au préalable les déclarer dans le champ « permissions » du manifeste. Par exemple, pour utiliser l’api storage de chrome, on déclare ici “storage”
Comme nous l’avons vu précédemment, le point de départ de l’extension réside dans le fichier Manifest.json. Je vous remets le mien ci-dessous :
{ "manifest_version": 2, "name": "BzZBuy", "description": "Hey BzZ BzZ help me to buy some stuff", "version": "1.0", "icons":{ "48":"bzZbuy.png" }, "background": { "scripts": ["background.js"] }, "browser_action": { "default_popup": "popup.html", "default_title": "bzZbuy" }, "content_scripts": [ { "matches": ["https://www.amazon.fr/*"], "js": ["content.js"], "css" : ["css/styles.css"] } ], "permissions": [ "webNavigation", "activeTab", "storage", "tabs" ] }
Tour d’horizon de l’interface et de son script
Dans la partie précédente, je vous avais conseillé de commencer par conceptualiser votre extension.
Voici mon scénario : admettons que je navigue sur Amazon et que je sois intéressé par un produit qui n’est plus disponible en stock :
Je veux que mon extension actualise la page jusqu’à ce que le produit soit en stock et, en fonction du prix que j’aurais choisi, ajoute l’article au panier puis finalise l’achat.
Pour cela il faut que l’extension permette de :
- saisir un montant maximum
- activer/désactiver le plugin via un toggle button
Je ne vous avais pas menti, vous pouvez créer une page web pour votre extension, voici la mienne :
Popup.html
<!DOCTYPE html> <html> <head> <meta charse="utf-8"> <link rel="stylesheet" href="css/styles.css"> <title> Auto Buy</title> </head> <body> <div class="popup"> <div> <div class="popup-header"> <h1> <div class="title"> BzZBuy for Amazon </div> <div class="close-icon"> <img src="close.svg" alt="close"> </div> </h1> </div> <div class="input-label"> <span>ENTER AN AMOUNT</span> </div> <div class="price-input"> <input type="input" class="form-field" placeholder="Amount in euro" name="price" id='price' required /> <label for="name" class="form-label">Amount in euro</label> </div> </div> <div id="start-btn" class="toggle-btn"> <input type="checkbox" class="toggle-value" /> <span class="round-btn"></span> </div> </div> <script src="popup.js"></script> <script src="jquery-3.5.1.min.js"></script> </body> </html>
Vous noterez ici que j’injecte jquery, cela nous sera grandement utile par la suite.
Je vous laisse gérer votre style css. Pour rappel, vous pouvez l’injecter via le content_script du manifest.
Maintenant que votre page html est créée, nous allons charger cette extension à chrome, puis nous passerons au javascript.
Ouvrez votre navigateur puis suivez les étapes de 1 à 3 :
Une fois sur la page des extensions, activez le mode développeur puis cliquez sur Charger l’extension non empaquetée et sélectionnez vos fichiers, n’oubliez pas le manifest.js. Votre plugin devrait apparaître dans la barre de navigation.
Popup.js
A présent, nous pouvons créer un nouveau fichier popup.js au sein duquel la première chose à faire est d’écouter l’événement DOMContentLoaded qui sera émis lorsque notre popup aura été complètement chargée.
document.addEventListener('DOMContentLoaded', () => { // ... });
On peut ensuite ajouter nos fonctionnalités, à savoir quelques vérifications de style ainsi que l’animation du toggle bouton.
$('.toggle-value').click(function () { if (!$('#price').val()) { $('#price').css("border-color", "red"); return; } if ($('#start-btn').hasClass('active')) { $('#start-btn').removeClass('active'); } else { $('#start-btn').addClass('active'); } }) document.getElementById("start-btn").addEventListener("click", function () { if (!$('#price').val()) { $('#price').css("border-color", "red"); return; }
Et voilà, donc rien de compliqué, on empêche l’utilisateur de valider tant qu’il n’a pas saisi un montant et on applique le style en conséquence.
Par contre, vous allez vite remarquer que les données saisies et le statut du bouton ne sont pas conservés. Si vous cliquez à nouveau sur votre extension, vous constaterez que vous êtes revenu à zéro.
Pour palier à cela, vous pouvez utiliser le storage de chrome :
storage.sync
chrome.storage.sync.set({ isActive: false, priceMax: null }, () => { console.log('chrome storage updated'); });
storage.local
chrome.storage.local.set({isActive: false, priceMax: null}, () => { console.log('chrome storage updated'); });
L’avantage avec le sync c’est que les données stockées seront automatiquement synchronisées avec n’importe quel navigateur Chrome auquel l’utilisateur est connecté, à condition d’avoir activé la synchronisation. Pratique quand on switch souvent d’un pc portable au pc de bureau.
Donc dans le cache, nous allons stocker deux variables à savoir le boolean concernant le plugin (actif ou non) et enfin le montant saisi dans l’input.
L’ajout du code est en rouge pour vous faciliter la lecture.
chrome.storage.sync.get(['isActive', 'priceMax'], ({ isActive, priceMax }) => { if (isActive) { $('.toggle-btn').addClass('active'); } $('#price').val(priceMax); }); $('.toggle-value').click(function () { if (!$('#price').val()) { $('#price').css("border-color", "red"); return; } if ($('#start-btn').hasClass('active')) { $('#start-btn').removeClass('active'); chrome.storage.sync.set({ isActive: false, priceMax: null }, () => { console.log('chrome storage updated'); }); } else { $('#start-btn').addClass('active'); chrome.storage.sync.set( { isActive: true, priceMax: $('#price').val() }, () => { console.log('chrome storage updated'); }); } })
Comme nous l’avons vu précédemment, nous avons besoin d’informer le content_script qui gère la page au moment où on active et désactive le toggle button. Pour se faire, c’est très simple, on va envoyer un message à celui-ci.
chrome.tabs.query({currentWindow: true, active: true}, (tabs) => { const activeTab = tabs[0]; $('#start-btn').hasClass('active')? chrome.tabs.sendMessage(activeTab.id, {"message":"start","price":$('#price').val()}): chrome.tabs.sendMessage(activeTab.id, {"message": "stop"}) })
Pour expliquer ce bout de code de manière simple, nous avons besoin de récupérer l’onglet actif. En effet, si nous sommes sur Amazon, sur la page d’un produit que l’on souhaite et qu’on active le toggle button, alors on veut envoyer un message au content_script pour lui transmettre le montant saisi.
chrome.tabs.query({currentWindow: true, active: true}, (tabs) => { const activeTab = tabs[0];
Ici on récupère l’onglet actif. Si cela ne fonctionne pas chez vous, vérifiez les permissions dans le manifest et ajoutez « activeTab » et « tabs ».
$('#start-btn').hasClass('active')? chrome.tabs.sendMessage(activeTab.id, { "message":"start","price":$('#price').val() }): chrome.tabs.sendMessage(activeTab.id, { "message": "stop" })
Et là, en fonction du bouton actif ou non, on transmet via la méthode sendMessage les informations souhaitées.
Content.js
Dans ce nouveau fichier, nous allons commencer par écouter le message envoyé depuis la popup puis, en fonction de son état (start ou stop), effectuer les actions voulues.
const activeUrl = window.location.href; chrome.runtime.onMessage.addListener( function (request, sender, sendResponse) { if (request.message === 'start') { const productUrl = activeUrl; chrome.storage.sync.set({ productUrl: productUrl, isActive: true, priceMax: request.price }, () => { }); autoBuy(productUrl, request.price); } if (request.message === 'stop') { resetState(); } } );
Dans le cas où l’action est à stop, on appelle la méthode resetState qui videra le cache.
const resetState = () => { chrome.storage.sync.set({ isActive: false, priceMax: null }, () => { chrome.storage.sync.set({ goToOrder: false }, () => { }); }) };
Si l’action est à start, on récupère l’URL de la page pour ensuite la stocker dans le cache avec l’état du bouton, ainsi que le prix saisi pour enfin appeler une méthode autoBuy.
const isInStock = document.getElementById('add-to-cart-button') || false; const ORDER_URL = 'https://www.amazon.fr/gp/buy/spc/handlers/display.html?hasWorkingJavascript=1'; const SELLER_PRICE = 'priceblock_ourprice'; function autoBuy(productUrl, maxPrice) { if (activeUrl !== productUrl) return; if (!(isInStock) && (window.location.href.indexOf('/dp/') > -1 || window.location.href.indexOf('/product/') > -1)) { window.location.reload(); } else { const price = parseFloat(document.getElementById(SELLER_PRICE).innerHTML.replace(' €', '')); if (price < maxPrice) { chrome.storage.sync.set({ productUrl: null, isActive: false, priceMax: null }, () => { chrome.storage.sync.set({ goToOrder: true }, () => { }); }); document.getElementById('add-to-cart-button').click(); chrome.runtime.sendMessage({ "message": "goToOrder", "url": ORDER_URL }); } } }
Dans la méthode autobuy(), on vérifie si le produit est en stock. Pour se faire, il faut inspecter la page à la recherche de l’id du bouton add-to-cart-button :
Si il n’existe pas et qu’on est bien sur la page d’un produit alors on actualise la page et on veut répéter ce raisonnement jusqu’à ce que le produit soit en stock.
if (!(isInStock) && (window.location.href.indexOf('/dp/') > -1 || window.location.href.indexOf('/product/') > -1)) { window.location.reload();
Une fois la page actualisée, il est nécessaire de rajouter ce bout de code qui permet de récupérer les informations du store pour relancer la méthode autoBuy.
chrome.storage.sync.get(['productUrl','isActive','priceMax'],({ productUrl, isActive, priceMax }) => { if (isActive) { autoBuy(productUrl, priceMax); } });
Cela peut paraître archaïque, mais c’est nécessaire afin d’éviter d’avoir le message d’alerte de détection de bot :
« Nous avons décelé que votre navigateur Web est une session robot et les sessions robot ne peuvent pas ajouter des articles au panier. »
Si le produit vient à être en stock, on passe alors dans le else. Dans la même logique, on inspecte la page pour récupérer l’id du prix afin de le comparer au prix maximum que l’on avait saisi dans notre extension.
else { const price = parseFloat(document.getElementById(SELLER_PRICE).innerHTML.replace(' €', ''));
Si le prix du produit est inférieur, alors on set à null les précédentes valeurs du store. On vient rajouter une nouvelles données au store à savoir un boolean goToOrder à true.
if (price < maxPrice) { chrome.storage.sync.set({ productUrl: null, isActive: false, priceMax: null }, () => { chrome.storage.sync.set({ goToOrder: true }, () => { }); }); document.getElementById('add-to-cart-button').click(); chrome.runtime.sendMessage({ "message": "goToOrder", "url": ORDER_URL });
On clique ensuite sur le bouton ajouter au panier, puis nous allons envoyer un message au background avec l’URL du paiement que nous avons stocké dans une variable ORDER_URL.
Background.js
Dans le background, on intercepte le message pour rediriger vers l’URL, on triche un peu avec le timeout car le fait d’ajouter l’article au panier redirige automatiquement vers le panier ce qui est problématique.
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { setTimeout(() => { if( request.message === "goToOrder" ) { chrome.tabs.update({ url: request.url }); } }, 2750) } );
De retour dans le fichier content.js on doit désormais rajouter ce bout de code :
chrome.storage.sync.get(['goToOrder'], ({ goToOrder }) => { if (goToOrder && url === ORDER_URL) { const finalOrder = document.getElementsByName(ORDER)[0]; checkExist(finalOrder, () => { chrome.storage.sync.set({ goToOrder: false }, () => { }); finalOrder.click(); }) }; }); const checkExist = (selector, callback) => { if (selector) { callback(); } else { setTimeout(() => { checkExist(selector, callback); }, 100); } }
On vérifie si l’URL correspond à celle du paiement. Si c est le cas, on check dans la page la présence du bouton :
A noter l’appel récursif à la méthode checkList pour vérifier son existence. Une fois présent, on clique sur ce bouton puis on reset les variables du store.
Et voilà votre extension est fin prête et fonctionnelle.
Pour conclure
Nous venons de voir comment créer une extension Chrome très facilement et avec peu de connaissances. Il ne vous reste plus qu’à faire appel à votre imagination pour trouver comment celle-ci peut vous être utile tant dans le domaine professionnel que personnel.
Malek CHAOUCHE