Firefox add-on programming
Contingut |
Introducció
Per què vull programar un add-on per al Firefox? Es tracta d'automatitzar la còpia d'informació de la web (Ctrl-A, Ctrl-C) i volcar-ho sobre un fitxer. Tot això amb la menor combinació de tecles possibles i sense haver de canviar de pantalla.
Notes prèvies
El meu objectiu és que tinc el plugin AppLauncher, que va molt bé, i el que vull és poder associar una drecera del teclat per executar aquest plugin. Si ho aconsegueixo serà una bona manera de guanyar rapidesa en la web data extraction.
...Doncs al final he trobat la solució de programar el meu propi add-on per executar un bash script i associar-li una drecera del teclat, per tant no necessito el add-on AppLauncher
Add-on SDK
Hi ha vàries aproximacions per progrmar add-ons, jo utilitzaré el SDK (que no és el XUL...)
Instal.lació:
El primer que faig és actualitzar el Firefox. Tenia la versió 11.0, i ara ja tinc la versió 19.0.
Em descarrego addon-sdk-1.13.2
$ source bin/activate
Canvia el prompt a:
(addon-sdk-1.13.2)joan@joanillo:~/Downloads/addon-sdk-1.13.2$
$ cfx testall
per córrer aquest test és necessari que el Firefox estigui actualitzat, i que la versió del Firefox es correpongui amb la versió del SDK.
Llegeixo la documentació bàsica, cfx init, cfx run.
Faig els exemples senzills: add-on clickme (Add a toolbar button) i l'exemple menuitems (Add a menu item to Firefox)
Em centro ja en l'aspecte que m'interessa: executar un bash script des del Firefox:
- http://stackoverflow.com/questions/11956753/how-to-get-set-working-directory-for-nsiprocess-runasync
- https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIProcess?redirectlocale=en-US&redirectslug=nsIProcess
i obtinc un resultat satisfactori. Ho ajunto amb l'exemple del Hotkeys per tal de programar les dreceres del teclat:
Associo l'execució del script a la combinació Alt-D, i això significa que amb el add-on customizable shortcuts que tinc instal.lat he d'alliberar la combinació Alt-D.
El add-on executar_script (~/Downloads/addon-sdk-1.13.2/packages/executar_script) queda de la següent forma:
main.js v1
main.js:
//http://stackoverflow.com/questions/11956753/how-to-get-set-working-directory-for-nsiprocess-runasync var { Hotkey } = require("sdk/hotkeys"); var showHotKey = Hotkey({ combo: "alt-d", onPress: function() { runBashScript("/home/joan/hola.sh"); } }); function runBashScript(scriptFile) { const {Cc,Ci} = require("chrome"); var localFile = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile) localFile.initWithPath('/bin/bash'); var process = Cc["@mozilla.org/process/util;1"] .createInstance(Ci.nsIProcess); process.init(localFile); var args = [ scriptFile ]; var rc = process.runAsync(args, args.length, function(subject, topic, data) { console.log('subject=' + subject + ', topic=' + topic + ', data=' + data); console.log('bash script finished executing, returned ' + process.exitValue); }); return rc; }
Estic utilitzant per provar-lo l'entorn SDK. Ara falta que funcioni en el Firefox de vertitat. S'ha de publicar.
- cfx xpi : build an installable XPI file to distribute your add-on
$ cfx xpi
ara ja tinc el fitxer executar_script.xpi
El directori on s'han d'instal.lar és efectivament /home/joan/.mozilla/firefox/mevel791.default/extensions/, però no funciona copiar tal qual el fitxer xpi en aquest directori. S'ha d'instal.lar. Hi ha vàries maneres de fer-ho manualment, però a mi el que m'ha funcionat és fer-ho de forma gràfica. Amb el Nautilus, des del directori packages/executar_script on tinc el executar_script.xpi, arrossego el fitxer fins al Firefox, i aleshores apareix el quadre de diàleg de la instal.lació dels add-ons. Efectivament queda instal.lat, però amb un nom diferent:
~/.mozilla/firefox/mevel791.default/extensions$ ls -la ... -rw-r--r-- 1 joan joan 90166 2013-02-26 01:19 jid1-pVzByfENzpRwGA@jetpack.xpi -rw-r--r-- 1 joan joan 100568 2013-02-26 01:19 jid1-ROwR0HGQBmUjKQ@jetpack.xpi
Funciona correctament.
main.js v2
Millora, ara podem cridar a un binari en comptes d'un script bash.
main.js:
//http://stackoverflow.com/questions/11956753/how-to-get-set-working-directory-for-nsiprocess-runasync var { Hotkey } = require("sdk/hotkeys"); var showHotKey = Hotkey({ combo: "alt-d", onPress: function() { //runBashScript("/home/joan/hola.sh"); runBinaryFile("/home/joan/jmining/bd/juridica/granollers/src/xclip_automation"); //runBashScript("/home/joan/hola2.sh"); } }); function runBashScript(scriptFile) { const {Cc,Ci} = require("chrome"); var localFile = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile) localFile.initWithPath('/bin/bash'); var process = Cc["@mozilla.org/process/util;1"] .createInstance(Ci.nsIProcess); process.init(localFile); var args = [ scriptFile ]; var rc = process.runAsync(args, args.length, function(subject, topic, data) { console.log('subject=' + subject + ', topic=' + topic + ', data=' + data); console.log('bash script finished executing, returned ' + process.exitValue); }); return rc; } function runBinaryFile(scriptFile) { const {Cc,Ci} = require("chrome"); var localFile = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile) localFile.initWithPath(scriptFile); var process = Cc["@mozilla.org/process/util;1"] .createInstance(Ci.nsIProcess); process.init(localFile); var args = [ ]; var rc = process.runAsync(args, args.length, function(subject, topic, data) { console.log('subject=' + subject + ', topic=' + topic + ', data=' + data); console.log('bash script finished executing, returned ' + process.exitValue); }); return rc; }
Ha costat bastant tenir un funcionament correcte. El binari C crida al script todo_granollers.sh:
#!/bin/bash win=$(xdotool search --title Wikijoan | head -1) xdotool windowactivate $win sleep 1 xdotool windowfocus $win xdotool key ctrl+a xdotool key ctrl+c xclip -o >> /home/joan/jmining/bd/juridica/granollers/src/todo_granollers.txt
i hem hagut d'introduir la comanda windowfocus i un sleep per tal de què funcioni correctament, o sigui que és una mica art fer-ho funcionar. Executar directament el script bash en comptes de fer-ho a través del binari C crec que no dóna tants de problemes.
Si el que vull és copiar el codi font de la pàgina web faré servir el scrpt todo_granollers2.sh:
#!/bin/bash win=$(xdotool search --title Wikijoan | head -1) xdotool windowactivate $win sleep 0.3 xdotool windowfocus $win xdotool key ctrl+u #codi font win=$(xdotool search --title Source | head -1) xdotool windowactivate $win sleep 0.3 xdotool windowfocus $win xdotool key ctrl+a xdotool key ctrl+c xclip -o >> /home/joan/jmining/bd/juridica/granollers/src/todo_granollers.txt sleep 0.3 xdotool key ctrl+w
Resum
Mode comanda
cd ~/Downloads/addon-sdk-1.13.2/
Primer de tot, he d'entrar en mode SDK
$ source bin/activate Welcome to the Add-on SDK. Run 'cfx docs' for assistance
Vaig a modificar el add-on que m'interessa:
cd packages/executar_script/ joe lib/main.js #modificar-lo cfx run #testejar-lo si vull comprovar el funcionament. cfx xpi #generar el xpi
Mode gràfic:
- Instal.lar el add-on copiant el fitxer directament al Firefox.
En la shell ja puc sortir del mode SDK:
(addon-sdk-1.13.2)joan@joanillo:~/Downloads/addon-sdk-1.13.2/packages/executar_script$ deactivate
pagweb2mail
Anem a fer el addon pagweb2mail com a compendi de tot allò vist. El problema que es planteja és que tinc una taula amb una llista de pagweb i sense mail. Per ex:
select pagweb from taula where (pagweb is not null or pagweb<>'') and (mail is NULL or mail='') and id > 300 limit 1 select pagweb, collegi, id from advocat where (pagweb is not null or pagweb<>'') and (mail is NULL or mail='') and cercar_mail is NULL and pagweb > '".$pagweb."' order by pagweb limit 1
La idea és que jo treballo amb el Firefox, i tinc definides per exemple dues hotkeys: alt-a i alt-d. Amb alt-a es pretén navegar a la següent pagweb. Quan estic a la pagweb cerco el mail (potser no està a index.htm sinó a contactar.htm, etc...). Quan veig que existeix el mail, aleshores amb alt-d faig una selecció del contingut (codi font). En qualsevol cas, s'ha d'alimentar un fitxer que serà processat a posteriori.
Primer de tot amb el SDK creem el projecte:
source /bin/activate cd packages mkdr pagweb2mail cfx init
El fitxer lib/main.js és l'important. Està buit. El podem copiar d'un altre projecte que ens serveixi com a plantilla.
El meu fitxer queda, després de proves i desenvolupament: main.js:
var pagweb; var tabs = require('sdk/tabs'); var tab = tabs[0]; var { Hotkey } = require("sdk/hotkeys"); var Request = require("sdk/request").Request; seguentPagweb(''); var showHotKey = Hotkey({ combo: "alt-d", onPress: function() { runBashScript("/home/joan/jmining/bd/juridica/general/pagweb2mail/pagweb2mail.sh", urlweb1); } }); var showHotKey = Hotkey({ combo: "alt-a", onPress: function() { seguentPagweb(toweb); } }); function seguentPagweb(urlweb) { console.log('Anem a cercar la següent de: ' + urlweb); var pagweb = Request({ url: 'http://localhost/jmining/automation/pagweb2mail.php?pagweb='+urlweb, overrideMimeType: "text/plain; charset=latin1", onComplete: function (response) { console.log(response.text); tab.url = response.text; toweb = response.text; urlweb1='#####' + toweb + '#####'; //enviaré aquesta informació al txt com a delimitador, juntament amb el codi font } }); pagweb.get();tab.url = response.text; } function runBashScript(scriptFile, urlweb) { const {Cc,Ci} = require("chrome"); var localFile = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile) localFile.initWithPath('/bin/bash'); var process = Cc["@mozilla.org/process/util;1"] .createInstance(Ci.nsIProcess); process.init(localFile); var args = [ scriptFile, urlweb]; var rc = process.runAsync(args, args.length, function(subject, topic, data) { console.log('subject=' + subject + ', topic=' + topic + ', data=' + data); console.log('bash script finished executing, returned ' + process.exitValue); }); return rc; }
La manera de funcionar és
cfx run
com sempre, i no val la pena instal.lar el plugin en el Firefox. Podem treballar directament en el SDK.
Explicació: quan iniciem el script cridem a la funció seguentPagweb();. Fa un request al servidor web i a la bd, pagweb2mail.php, buscant la següent pàgina web que tingui el camp cercar_mail is NULL (estic buscant possibles mails dins una llista de pagweb). El script php em retorna la següent pagweb, i naveguem a aquesta pagweb amb la sentència:
tab.url = response.text;
i a més fico aquesta pagweb en dues variables, toweb i urlweb1, aquesta última dins dels caràcters ##### que em servirà de delimitador (veure més endavant).
Un cop carregada la pagweb he de navegar pel site buscant la pàgina que contingui el mail. Pot estar en la pàgina d'entrada (index.html o la que sigui), o pot estar a contactar.html... Quan la trobo s'ha definit el hotkey alt-d que executa el script /home/joan/jmining/bd/juridica/general/pagweb2mail/pagweb2mail.sh passant-li com a argument urlweb1.
Fitxer pagweb2mail.sh:
#!/bin/bash echo >> /home/joan/jmining/bd/juridica/general/pagweb2mail.txt echo $1 >> /home/joan/jmining/bd/juridica/general/pagweb2mail.txt win=$(xdotool search --onlyvisible --name Firefox | head -1) xdotool windowactivate $win sleep 0.3 xdotool windowfocus $win xdotool key ctrl+u #codi font win=$(xdotool search --title Source | head -1) xdotool windowactivate $win sleep 0.3 xdotool windowfocus $win xdotool key ctrl+a sleep 0.3 xdotool key ctrl+c xclip -o >> /home/joan/jmining/bd/juridica/general/pagweb2mail.txt sleep 0.3 xdotool key ctrl+w
Veiem quina és el directori del projecte on està el script i on omplirem la informació: /home/joan/jmining/bd/juridica/general/. Aquest script s'encarrega de buscar el codi font (ctrl-u), i fer seleccionar tot (ctrl-a), copiar (ctrl-c), i omplir el fitxer pagweb2mail.txt que serà processat a posteriori. Finalment ctrl-w per tancar la pàgina del codi font.
Ja puc anar a la següent pàgina amb alt-a, que crida a la funció que havíem cridat al principi, seguentPagweb(toweb), però ara li passem la pagweb actual, i el response del script php ens donarà la pagweb següent, i tornem a començar.
A mida que anem processant les pàgines el script php fica el camp cercar_mail=1, indicant que aquesta pagweb ja l'hem processat i per tant no cal tornar-ho a fer. D'aquesta manera ens evitem repetir la feina.
El script pagweb2mail.php del que hem parlat queda de la següent manera: pagweb2mail.php:
<?php include("../open_db.php"); ?> <?php //select pagweb, collegi, id from advocat where (pagweb is not null or pagweb<>'') and (mail is NULL or mail='') and cercar_mail is NULL and pagweb > 'www.solermore.com' order by pagweb; $pagweb = $_GET["pagweb"]; $sql = "select pagweb, collegi, id from advocat where (pagweb is not null or pagweb<>'') and (mail is NULL or mail='') and cercar_mail is NULL and pagweb > '".$pagweb."' order by pagweb limit 1"; //echo $sql; $result = mysql_query($sql); if (!$result) { $message = 'Invalid query: ' . mysql_error() . "\n"; die($message); } while ($row = mysql_fetch_assoc($result)) { echo $row['pagweb']; //hauré de fer update advocat set cercar_mail=1 where ... per tal de marcar que aquesta pagweb ja l'he processada. $sql = "update advocat set cercar_mail=1 where pagweb='".$row['pagweb']."'"; //echo $sql; $result2 = mysql_query($sql); } ?> <?php include("../close_db.php"); ?>
Aquest és un mètode general per cercar mails allà on tenim llistes de pagwebs en una taula. Ara falta processar el fitxer pagweb2mail.txt amb el binari pagweb2mail.c per tal d'omplir el fitxer pagweb2mail.sql on hi haurà els updates per omplir la base de dades amb els mails trobats, del tipus:
update taula set mail='pepito@pepito.com' where pagweb='www.pepito.com'
És bo que el procés sigui semimanual/semiautomàtic, doncs tot el procés des del principi fins al final és procliu a errors.
Un altre exemple: cercar_mails
Tinc una taula amb uns registres que sé que tenen mail, però a la pàgina web no s'hi pot accedir pel codi font. La única manera és fent Ctrl-A, Ctrl-C de la pàgina, no del codi font (on no apareix el mail). Aquesta és una manera d'obtenir informació allà on AJAX no deixa cercar la informació.
A més, com a novetat, aquest script és desatès, va fent la select sobre la taula. L'usuari no interacciona amb l'ús de les tecles com en el cas anterior. Es pot interrompre i es reinicia allà on es va acabar. I al final de tot quan ha acabat la feina tanca el script de forma educada. La novetat d'aquest script és l'ús de dos timers. Amb el primer es pretén esperar un temps prudencial per tal de què es carregui la pàgina. Quan s'ha acabat de carregar la pàgina cridem al script bash que farà la tasca de copiar i enganxar a un fitxer el text de la web; alhora que iniciem un altre timer que ens portarà a la següent web.
main.js:
var pagweb; //https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsITimer?redirectlocale=en-US&redirectslug=nsITimer const {Cc,Ci} = require("chrome"); var timer1 = Cc["@mozilla.org/timer;1"] .createInstance(Ci.nsITimer) var timer2 = Cc["@mozilla.org/timer;1"] .createInstance(Ci.nsITimer) var tabs = require('sdk/tabs'); var tab = tabs[0]; var system = require("sdk/system"); var Request = require("sdk/request").Request; seguentPagweb(''); function seguentPagweb(urlweb) { console.log('Anem a cercar la següent de: ' + urlweb); var pagweb = Request({ url: 'http://localhost/jmining/automation/cercar_mails_advocats_tenerife.php?extra='+urlweb, overrideMimeType: "text/plain; charset=latin1", onComplete: function (response) { console.log(response.text); tab.url = 'file:///home/joan/jmining/bd/juridica/tenerife/src/' + response.text; toweb = response.text; if (strcmp(toweb,'reg')==1) { timer1.initWithCallback(event1,4000, Ci.nsITimer.TYPE_ONE_SHOT); } else { console.log('*** FI DEL SCRIPT ***'); //quit the host application: //https://addons.mozilla.org/en-US/developers/docs/sdk/1.13/modules/sdk/system.html system.exit(); } } }); pagweb.get(); } function runBashScript(scriptFile) { const {Cc,Ci} = require("chrome"); var localFile = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile) localFile.initWithPath('/bin/bash'); var process = Cc["@mozilla.org/process/util;1"] .createInstance(Ci.nsIProcess); process.init(localFile); var args = [ scriptFile ]; var rc = process.runAsync(args, args.length, function(subject, topic, data) { console.log('subject=' + subject + ', topic=' + topic + ', data=' + data); console.log('bash script finished executing, returned ' + process.exitValue); }); return rc; } var event1 = { notify: function(timer) { console.log('ja puc executar el script'); runBashScript("/home/joan/jmining/bd/juridica/tenerife/src/cercar_mails_advocats_tenerife.sh"); console.log('ja puc anar al timer2'); timer2.initWithCallback(event2,4000, Ci.nsITimer.TYPE_ONE_SHOT); } } var event2 = { notify: function(timer) { console.log('ja puc anar a ' + toweb); seguentPagweb(toweb); } } function strcmp ( str1, str2 ) { // http://kevin.vanzonneveld.net // + original by: Waldo Malqui Silva // + input by: Steve Hilder // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + revised by: gorthaur // * example 1: strcmp( 'waldo', 'owald' ); // * returns 1: 1 // * example 2: strcmp( 'owald', 'waldo' ); // * returns 2: -1 return ( ( str1 == str2 ) ? 0 : ( ( str1 > str2 ) ? 1 : -1 ) ); }
Per completar el codi falta el script php que fa la consulta a la base de dades; i el bash script. És un cas acadèmci que s'ha fet per trobar advocats de Tenerife.
Com a resultat es genera un fitxer txt que es processa per obtenir la informació. A diferència del cas de Granollers, ara no tenim el codi font, sinó senzillament el text de les pàgines. Amb això après es pot millorar el codi de Granollers per tal de fer-lo desatès.
creat per Joan Quintana Compte, febrer 2013