lunes, 15 de agosto de 2016

Ejemplo de programación de extensiones para Chrome

El aburrimiento es la enfermedad de las personas afortunadas; los desgraciados no se aburren, tienen demasiado que hacer.(A. Dufresnes)

Ayer por la tarde estaba aburrido (muy aburrido), y se me ocurrió para pasar el rato desarrollar una extensión para el navegador Chrome; de manera que al mismo tiempo de quitarme el aburrimiento, aprendía algo nuevo (y quizás útil para el futuro): en concreto, me propuse estudiar el modo en que podría interactuar programáticamente, usando jQuery; sobre una pestaña cualquiera abierta en el navegador.

Como ejemplo; se me ocurrió utilizar la versión web de Whatsapp (https://web.whatsapp.com/) para, desde el navegador, crear y cargar una extensión (plugin) capaz de buscar en una conversación concreta de un contacto mio su última respuesta, y de responderle en consecuencia de manera autónoma usando una API de inteligencia artificial conversacional externa (finalmente me decidí por https://www.cleverbot.com/ porque fue el único que encontré capaz de hablar en castellano).

Finalmente lo conseguí, y el resultado final ha sido el deseado: una extensión capaz de leer el último comentario que un contacto me haya escrito, y responder automáticamente usando el motor de IA de Cleverbot. Aunque, por desgracia, Cleverbot en castellano no está demasiado bien conseguido y a veces se lía con respuestas poco realistas. Vamos, que no va a pasar el test de Turing con vuestros amigos (quizás si tenéis amigos que hablen inglés lo haga más decentemente) :P.

Explicación técnica.

Paso a continuación a dar algunas explicaciones técnicas para que cualquier interesado pueda hacer la prueba él mismo. Los pasos son muy sencillos:

Os bajáis todo el código fuente de la extensión desde aquí, y lo descomprimís todo en una carpeta. El resultado será el siguiente:


En el manifest.json vamos a especificar los datos generales de la extensión: nombre, versión. permisos, etc.


{
    "name": "Chat de Turing",
    "version": "1.5",
    "manifest_version": 5,
    "background": {
        "scripts": ["init.js"]
    },
    "page_action": {
        "default_icon": "icon.png"
    },
    "permissions": ["tabs", "http://*/*", "https://*/*"]
}
manifest.json

El fichero init.js, el cual hará de background del plugin de Chrome, será el encargado de montar el framework jQuery y el fichero principal con el script de contenidos: content.js:


function checkForValidUrl(tabId, changeInfo, tab) {
    chrome.pageAction.show(tabId);
}

chrome.tabs.onUpdated.addListener(checkForValidUrl);

chrome.pageAction.onClicked.addListener(function(tab) {
 chrome.tabs.executeScript(tab.id, { file: "jquery.js" }, function() {
  chrome.tabs.executeScript(tab.id, { file: "content.js" });
 });
});
init.js

Por último, os muestro el fichero de contenidos con el Javascript encargado de leer la última respuesta del contacto y de contestar a la misma usando el motor de inteligencia artificial del que ya hemos hablado:


function getSaludo()
{
 var textArray = [
  "como estas?",
  "que tal estas?",
  "hola que tal?",
  "holaa como estas?",
  "dime algo",
  "dime algo",
  "buenas",
  "hola",
  "que tal?",
  "hola",
 ];
 return textArray[Math.floor(Math.random() * textArray.length)];
}

function randomIntFromInterval(min, max)
{
    return Math.floor(Math.random()*(max-min+1)+min);
}

function dispatch(target, eventType, char) {
 var evt = document.createEvent("TextEvent");    
 evt.initTextEvent (eventType, true, true, window, char, 0, "es-ES");
 target.focus();
 target.dispatchEvent(evt);
}

function triggerMensaje() {
 var event = new MouseEvent('click', {
   'view': window,
   'bubbles': true,
   'cancelable': true
  });
  
 document.querySelector(".icon.btn-icon.icon-send").dispatchEvent(event);
}

function llamarClaverBot(texto)
{
 $.ajax({
        url: "https://xxxxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),
        type: "get",        
        success: function (response) {               
     console.log(response);
     dispatch(document.querySelector("div.input"), "textInput", response);
     triggerMensaje(); 
        },
        error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus, errorThrown);
        }
    }); 
}
 
function actuar()
{
 var p = Math.floor(Math.random() * 100) + 1; 
 var ultimo = $( ".message-in .message-text" ).last().text();
 
 if(ultimo !== "")
 {
  ultimo = ultimo.split("]")[1];
  ultimo = ultimo.split(":")[1];
 }
 
 if(ultimo && ultimo !== "" && ultimo != ultima_respuesta)
 {
  ultima_respuesta = ultimo;
  llamarClaverBot(ultimo);
 }
 else if(!iniciado)
 {   
  dispatch(document.querySelector("div.input"), "textInput", getSaludo());
  triggerMensaje(); 
 }
 
 iniciado = true;   
}

var iniciado = false, ultima_respuesta = "";

$(document).ready(function()
{
 setInterval(actuar, 15000);
});
content.js

Como  podéis ver, el código fuente es muy sencillo, y se trata simplemente de montar un temporizador que cada cierto tiempo (15000 milisegundos), va a ir mirando el DOM de la página web de nuestro Whatsapp, buscando por si nuestro contacto nos ha respondido algo nuevo:

var ultimo = $( ".message-in .message-text" ).last().text();

Y si es ese el caso (ultimo != ultima_respuesta) y el algoritmo encuentra una nueva respuesta, se llama por AJAX a la IA de CleverBot y se captura la respuesta del mismo:

    $.ajax({
        url: "https://xxxxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),
        type: "get",        
        success: function (response) {                 
     console.log(response);
     dispatch(document.querySelector("div.input"), "textInput", response);
     triggerMensaje(); 
        },
        error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus, errorThrown);
        }
    }); 

Finalmente, se despacha esta respuesta modificando programáticamente el DOM de la página simulando las pulsaciones del teclado para escribir la respuesta (usando el evento "TextEvent"), y simulando también una llamada al botón de enviar de la página buscando por jQuery el botón (".icon.btn-icon.icon-send") y forzando un evento 'click' sobre él:

        var event = new MouseEvent('click', {
   'view': window,
   'bubbles': true,
   'cancelable': true
  });
  
 document.querySelector(".icon.btn-icon.icon-send").dispatchEvent(event);

Nota:
Podéis ver que en la función llamarCleverBot() se realiza una llamada AJAX a un servidor externo pasando como parámetro GET la respuesta de nuestro contacto. Este servidor externo debéis montarlo vosotros subiendo la carpeta "bot" que os he mostrado arriba a cualquier servidor web que tengáis por ahí (valen los gratuitos que se ofrecen por Internet, siempre y cuando tenga seguridad SSL; es decir protocolo https).

Modo de uso.

¿Cómo usar esta extensión del navegador? Muy sencillo:

1) Descomprimís el fichero y subís la carpeta "bot" a algún servidor web  con protocolo https activo que tengáis por ahí.

2) En el fichero content.js modificáis la url de la llamada a CleverBot para que apunte a vuestro servidor web. Es decir; que debéis cambiar las xxxxx de esta línea por el dominio de vuestro servidor:

url: "https://xxxxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),


3) Vais a vuestro navegador Chrome, abrís una nueva pestaña, y escribís en la barra de direcciones lo siguiente: chrome://extensions/ y pulsáis el botón del "Entrar".


Activáis el "Modo de desarrollador" y pulsáis en el botón "Cargar extensión descomprimida...", tras lo cual debéis buscar y seleccionar la carpeta donde habéis descomprimido todo el código fuente del fichero.

El resultado final deberá ser similar al de la imagen anterior, donde aparecerá una nueva extensión (comprobad que esté "Habilitada") llamada "Chat de Turing", que es el nombre que le dimos en el manifest.json a nuestra extensión.



4) Creáis una nueva pestaña en el navegador y abrís en ella la web de Whatsapp: https://web.whatsapp.com/ (si no lo habéis hecho antes, deberéis seguir unas pequeñas instrucciones para conectar a vuestro Whatsapp en modo Web):



5) Si habéis seguido bien las instrucciones, os aparecerá arriba a la derecha de la barra de direcciones un icono con el logo de Whatsapp (junto a la estrella para añadir a favoritos). Simplemente debéis pulsar en dicho icono y el script se activará.

6) Por último, entrad en alguna conversación con algún amigo o grupo y saludar. En cuanto te responda, el plugin que hemos montado detectará su respuesta y le replicará usando la IA conversacional de CleverBot.

Resultados.

Pues como ya os he comentado, por desgracia CleverBot no se encuentra en el estado del arte en cuanto a su capacidad para conversar de manera inteligente; pero sí es cierto que a veces tiene respuestas muy ocurrentes, y yo al menos me he hartado de reír en la conversación que ha tenido con un par de amigos míos (y en grupos con mucha cantidad de gente hablando).

Total, ha sido un pequeño divertimento que realicé en unas pocas horas de aburrimiento. Espero que al menos sea útil a alguien. Yo, por ejemplo; he aprendido mucho sobre la programación (y el potencial) de las extensiones Chrome; y en mi profesión todo lo que se aprende tarde o temprano puede llegar a servir para algo ;).

Os dejo para finalizar un pequeño ejemplo de conversación que tuvo el bot con una amiga:



Un saludo.

Edito: 16/08/2016
------------------------------------------------------------------------------------------------------------------------------------

Para probar un poco mejor la capacidad real de Cleverbot para confundir a un humano y hacerle pensar que habla con otra persona, he decidido modificar el código fuente para que el bot tenga una conversación privada con un desconocido cualquiera del chat genérico elchat.com.

El resultado ha sido asombroso. El bot se ha comportado, como era de esperar, mucho mejor con desconocidos que con mis contactos de Whatsapp, que me conocen ya y se huelen enseguida que estoy diciendo cosas raras. Pero como os digo, con desconocidos la cosa cambia: la verdad es que me he hartado de reír. Os voy a pegar una conversación de ejemplo entre un tal LUIS LA7294 y nuestro bot, al que llamé Mary4840. Por momentos es una conversación incluso interesante, como cuando el chico le habla en portugués y el bot es capaz de seguirle la corriente hablando también en portugués. De hecho, pasó tan bien el "test de Turing" en este caso concreto, que tuve que cortar la conversación al cabo de 15 minutos aburrido ya:

Mary4840: Hello
LUIS LA7294: hola
Mary4840: ¿Puedo preguntarte algo?
LUIS LA7294: si claro
Mary4840: Como estas?
LUIS LA7294: bien gracias y tu
Mary4840: Muy Bien
LUIS LA7294: pero me ivas a preguntar algo
LUIS LA7294: que es
Mary4840: Que es que?
LUIS LA7294: pues lo ke me ivas a preguntar
Mary4840: No te iba a preguntar nada
LUIS LA7294: me dijistes puedo preguntarte algo
Mary4840: Si
LUIS LA7294: si si puedes
LUIS LA7294: a ver ke es
Mary4840: Que cosa?
LUIS LA7294: nada olvidalo
LUIS LA7294: de donde te comunicas
Mary4840: Que importa
LUIS LA7294: si si me interesa de dond ete comunicas
Mary4840: Alguna vez probaste carne humana?
LUIS LA7294: quien kite y vivamos cercas
LUIS LA7294: la verda asta ahorita no
Mary4840: Soy mujer
LUIS LA7294: ke acaso tu ya
Mary4840: Si
LUIS LA7294: como hacer el amor
LUIS LA7294: o comertela
Mary4840: Comerme el que?
LUIS LA7294: pues la cerne humanana
Mary4840: Quien gano la final de la copa brasil 2014?
LUIS LA7294: alemania
LUIS LA7294: mary de donde eres
Mary4840: Colombia
LUIS LA7294: ok
LUIS LA7294: y donde vives ahora
Mary4840: Barcelona
LUIS LA7294: ok y ke haces aya
Mary4840: A juego para un equipo de futbol
LUIS LA7294: mandame uan fotomary me gustaria conocerte
LUIS LA7294: que buena onda
Mary4840: Vos sos hombre?
LUIS LA7294: si asi es
Mary4840: De donde eres?
LUIS LA7294: de los angeles ca
LUIS LA7294: mandame uan fot para conicerte
Mary4840: No!!!!
LUIS LA7294: y eso por ke no
Mary4840: Yo tengo un elefante que se llama trompita
LUIS LA7294: oye mary kiero conocerte
Mary4840: Yo no
LUIS LA7294: mandam euan foto
LUIS LA7294: y eso por ke no kieres conocerme
Mary4840: Sí quiero
LUIS LA7294: ok
LUIS LA7294: pues mandame una foto
Mary4840: Como?
LUIS LA7294: pues aki
Mary4840: Que asco, no sabes escribir?
LUIS LA7294: si se por ke dices eso
Mary4840: Por que eres genial
LUIS LA7294: ok
LUIS LA7294: pues mandam euna foto
Mary4840: Es congruente lo que dices
LUIS LA7294: si asi es
Mary4840: Muy bien
LUIS LA7294: mandamelal please
Mary4840: Non so come si fa
LUIS LA7294: sabes portuges
Mary4840: Sim
LUIS LA7294: você pode falar em Português
Mary4840: Estou falando em português
LUIS LA7294: envie-me uma imagem
Mary4840: No puedo
LUIS LA7294: porque não
Mary4840: Porque nao nao é resposta
LUIS LA7294: por favor
Mary4840: Por favor que
LUIS LA7294: envie-me uma imagem
Mary4840: No puedo
LUIS LA7294: por ke no puedes
Mary4840: Eso es lo que te pregunto
LUIS LA7294: tu mandala
Mary4840: Quien manda?
LUIS LA7294: tu mandame uan fot tuya
Mary4840: Tu primero
LUIS LA7294: omg
LUIS LA7294: \yopedi primero asi ke mandala tu primero
Mary4840: Yo las pedí primero
LUIS LA7294: no fui yo
Mary4840: Quien fue?
LUIS LA7294: yo fui
Mary4840: A dónde
LUIS LA7294: el primero en pedirte la foto
Mary4840: No tu primero
LUIS LA7294: tu primero
LUIS LA7294: m ela vas a mandar si o no
Mary4840: No
LUIS LA7294: ok



Por cierto, que si queréis probar esta versión de la extensión, simplemente debéis sobrescribir todo el contenido del fichero content.js anterior por lo siguiente:

function getSaludo()
{
 var textArray = [
  "como estas?",
  "que tal estas?",
  "hola que tal?",
  "holaa como estas?",
  "dime algo",
  "dime algo",
  "buenas",
  "hola",
  "que tal?",
  "hola",
 ];
 return textArray[Math.floor(Math.random() * textArray.length)];
}

function randomIntFromInterval(min, max)
{
    return Math.floor(Math.random()*(max-min+1)+min);
}

function triggerMensaje() {
 $('.topcmm-123flashchat-common-chat-panel-input-box-send-btn').click(); 
 $('#topcmm-123flashchat-private-input-container > textarea').focus(); 
}

function llamarClaverBot(texto)
{
 $.ajax({
        url: "https://xxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),
        type: "get",        
        success: function (response) {             
     console.log(response);
     $('#topcmm-123flashchat-private-input-container > textarea').val(response);
     ultima_respuesta = response;
     triggerMensaje(); 
        },
        error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus, errorThrown);
        }
    }); 
}
 
function actuar()
{
 var p = Math.floor(Math.random() * 100) + 1; 
 var ultimo = $( ".topcmm-123flashchat-private-chat-box .topcmm-123flashchat-message-content-style" ).last().text();  
 
 if(ultimo && ultimo !== "" && ultimo != ultima_respuesta)
 {
  ultima_respuesta = ultimo;
  llamarClaverBot(ultimo);
 }
 else if(!iniciado)
 {
  ultima_respuesta = getSaludo();
  $('#topcmm-123flashchat-private-input-container > textarea').val(ultima_respuesta);
  triggerMensaje(); 
 }
 
 iniciado = true;   
}

var iniciado = false, ultima_respuesta = "";

$(document).ready(function()
{
 setInterval(actuar, 25000);
});

Recordad modificar la url que llama a la API externa de Cleverbot por el dominio donde hayáis subido la librería PHP (arriba tenéis más información sobre esto).

Un saludo!!

No hay comentarios:

Publicar un comentario en la entrada