Crea un chatbot para las salas de chat de Stack Exchange

39

El reto

El objetivo de este desafío es crear un chatbot que pueda ejecutarse en las salas de chat de Stack Exchange. Su bot debe poder detectar cuándo un usuario publica comandos específicos y responder a ellos. Esta es la lista de comandos y lo que debe hacer su bot:

  • !!newest: muestra el título (sin enlace, pero el título) de la última pregunta publicada en este sitio (codegolf.SE).
  • !!metanewest: muestra el título de la pregunta más reciente publicada en el sitio meta (meta.codegolf.SE).
  • !!questioncount: muestra el recuento de preguntas actual.
  • !!metaquestioncount: genera el recuento de preguntas actual en el meta sitio.
  • !!tag tagname: muestra el extracto de la etiqueta (la breve descripción) de la etiqueta que se proporciona como primer parámetro.
  • !!metatag tagname: igual que el anterior, pero para el meta sitio.
  • !!featured: genera el recuento de preguntas que actualmente tienen una recompensa.
  • !!metafeatured: genera el recuento de preguntas que tienen la etiqueta [destacado] en Meta.

Reglas

  1. Debe escribir un programa completo, no un fragmento o función.
  2. En caso de que sea necesario, puede solicitar nombre de usuario y contraseña como entrada (solicitando entrada, STDIN, argumentos de línea de comandos). Esto será necesario si usa, por ejemplo, Python o Ruby, pero no será necesario si usa JavaScript y ejecuta el script en la página de la sala de chat.
  3. Puede usar bibliotecas externas para hacer cosas como WebSockets. Estas bibliotecas no tienen que contar para su recuento de caracteres.
  4. Usted puede usar una envoltura externa de chat (pero no tiene que, a escribir su propia se anima), y luego de que tiene que contar para el recuento de caracteres. Tampoco se le permite cambiar el código del contenedor. Si lo usa, lo usa sin modificaciones y todos los caracteres deben contarse (eso es una penalización por no escribir su propio contenedor).

    Solo el código del envoltorio tiene que contar. Si hay otros archivos como ejemplos, estos no tienen que contar.

  5. No se pueden usar acortadores de URL u otras formas que puedan acortar las URL: el desafío es jugar un chatbot, no jugar una URL.
  6. No hay solicitudes web, excepto las necesarias para chatear y obtener la información necesaria para responder a los comandos.
  7. El uso de las "lagunas" estándar no está permitido.
  8. Si alguien envía un comando, es necesario responder con un mensaje de chat de este formato: @user response. Entonces, si escribo el comando !!featuredy hay 5 preguntas destacadas, su bot debería publicar @ProgramFOX 5.
  9. Si pruebo tu bot, lo ejecutaré desde mi cuenta de chatbot y lo ejecutaré en esta sala de chat . Siempre probaré los bots en esa sala, por lo que no es necesario proporcionar la ID de la sala como entrada, siempre será 14697. Esta ID no se proporcionará como entrada, debe estar codificada.
  10. Si no se encuentra el comando, salida @user The command [command] does not exist. Reemplace [command]por el nombre del comando no existente. Si se proporcionan argumentos al comando, no muestre los argumentos, solo el nombre del comando.
  11. Si un comando tiene muchos argumentos, ignore los argumentos que no son necesarios.
  12. Si un comando no tiene suficientes argumentos, salida @user You have not provided enough arguments
  13. El sistema evita que se publiquen mensajes duplicados dentro de un rango de tiempo corto. Entonces, cuando pruebe su bot, nunca ejecutaré dos comandos que den la misma salida sucesivamente (lo que significa que no tiene que implementar un sistema que diferencie los mensajes si son duplicados, agregando un punto, por ejemplo).
  14. El sistema evita que se publiquen demasiados mensajes dentro de un rango de tiempo corto, por lo tanto, cuando realice las pruebas, nunca enviaré demasiados comandos dentro de un rango de tiempo corto, lo que significa que su bot no tiene que ocuparse de esto (esperando un tiempo antes de publicar, por ejemplo).
  15. Esto es , el programa con la menor cantidad de bytes gana.

Empezando

Aquí hay información para comenzar a escribir su bot. No tiene que usar esto, pero puede ser una guía.

  • Para iniciar sesión, primero inicie sesión en un proveedor de OpenID. Esto siempre será Stack Exchange OpenID ( https://openid.stackexchange.com). El formulario de inicio de sesión se encuentra en https://openid.stackexchange.com/account/login, y proporciona el nombre de usuario y la contraseña allí.
  • Luego, inicie sesión en stackexchange.com. El formulario de inicio de sesión se encuentra en https://stackexchange.com/users/login. Elija Stack Exchange como proveedor de OpenID.
  • Después de hacer eso, inicia sesión para chatear. El formulario de inicio de sesión para eso se encuentra en http://stackexchange.com/users/chat-login. Elija Stack Exchange como proveedor de OpenID.
  • Entonces necesitas conseguir tu fkey. Para eso, vaya http://chat.stackexchange.com/chats/join/favoritey obtenga el fkeyde un campo de entrada oculto.
  • Para publicar un mensaje, envíe una solicitud http://chat.stackexchange.com/chats/14697/messages/newy proporcione dos parámetros POST: un textparámetro que contiene el texto del mensaje y un fkeyparámetro que contiene el fkey.
  • Para ver cuándo se publica un nuevo mensaje, puede usar WebSockets (pero no tiene que hacerlo, siéntase libre de usar algo más si es más corto). Consulte esta respuesta de Meta Stack Exchange :

    Charla

    (wss://chat.sockets.stackexchange.com/events/<roomnumber>/<somehash>?l=<timethingy>)

    El hash se puede recuperar PUBLICANDO el ID de la sala y la tecla f http://chat.stackexchange.com/ws-auth

    El timethingy es la clave de tiempo del json devuelto por /chats/<roomno>/events.

    La identificación del evento cuando se publica un mensaje es 1.

  • Es útil mirar los contenedores de chat existentes, como StackExchange-Chatty de Doorknob y ChatExchange de Manishearth , para ver cómo funciona exactamente.
ProgramFOX
fuente
3
En el momento en que vi el título, instantáneamente pensé "ah, ProgramFOX".
seequ
Esperaba metafeaturedque significara preguntas abundantes sobre meta, pero ... gracias :-)
John Dvorak
@JanDvorak Las metas por sitio no tienen recompensas, por lo que no puedo usar eso. Cuando escribí este desafío, olvidé que Meta tenía una etiqueta [destacada], ¡así que gracias por su sugerencia!
ProgramFOX
Lo que hice para ver si se publicó un nuevo mensaje fue verificar cada 2 segundos a través de JS si el último mensaje no era mío (último elemento de la clase)
Cilan
Ya tenemos uno aquí
Sr. Alien

Respuestas:

14

JavaScript + jQuery, 1362 1258 bytes

Golfé usando un minificador:

$(function(){function e(){function e(e,t){$("#input").val("@"+$(e).parents(".user-container").find(".username").eq(0).text()+" "+t),$("#sayit-button").click()}var i,a=$(t),s=a.map(function(e,t){return t.id}),r=s.slice(-1)[0]
n!=r&&(i=a.slice($.inArray(n,s)+1),n=r,i.map(function(t,n){var i,a,s,r,o,u,c,f=n.textContent.match(/!!(\S+)(?:\s+(\S+))?/)
if(f){switch(i=f[1],a=f[2],s="codegolf",0==i.indexOf("meta")&&(s="meta."+s,i=i.slice(4)),r="?site="+s,c=0,i){case"newest":o=["questions","&order=desc&sort=creation"],u=function(e){return e.items[0].title}
break
case"questioncount":o=["info",""],u=function(e){return e.items[0].total_questions}
break
case"tag":if(!a){c=1
break}o=["tags/"+a+"/wikis",""],u=function(e){return 0==e.items.length?"Tag not found":e.items[0].excerpt}
break
case"featured":o=0==s.indexOf("meta.")?["questions","&tagged=featured"]:["questions/featured",""],u=function(e){var t=e.items.length
return(e.items.has_more?"more than ":"")+t}}c?e(n,"You have not provided enough arguments"):o?$.get("http://api.stackexchange.com/2.2/"+o[0]+r+o[1],function(t){e(n,u(t))}):e(n,"The command "+i+" does not exist")}}))}var t="[id^=message-]",n=$(t).eq(-1).attr("id")
new MutationObserver(e).observe($("#chat").get(0),{childList:!0,subtree:!0})})

Debe ejecutar la secuencia de comandos directamente en el navegador (utilizando los trabajos jQuery de Stack Exchange):

  1. Abra http://chat.stackexchange.com/rooms/14697/chatbot-challenge-on-programming-puzzles-code-golf
  2. Pegue el código anterior en la consola
  3. Ingrese algunos comandos en el chat

Se podía jugar mucho más al golf, pero no podía molestarse.


Sin golf:

$(function() {
    var sel = '[id^=message-]';
    var latestMessage = $(sel).eq(-1).attr('id');
    function update() {
        var messages = $(sel);
        var ids = messages.map(function(i, x) { return x.id; });
        var newest = ids.slice(-1)[0];
        if(latestMessage == newest) {
            return;
        }
        var newMessages = messages.slice($.inArray(latestMessage, ids) + 1);
        latestMessage = newest;
        newMessages.map(function(i, x) {
            var m = x.textContent.match(/!!(\S+)(?:\s+(\S+))?/);
            if(!m) {
                return;
            }
            var c = m[1];
            var a = m[2];
            var s = 'codegolf';
            if(c.indexOf('meta') == 0) {
                s = 'meta.' + s;
                c = c.slice(4);
            }
            var site = '?site=' + s;
            var url;
            var extractor;
            var too_few_args = 0;
            switch(c) {
                case 'newest':
                    var url = ['questions', '&order=desc&sort=creation'];
                    extractor = function(data) {
                        return data.items[0].title;
                    };
                    break;
                case 'questioncount':
                    url = ['info', ''];
                    extractor = function(data) {
                        return data.items[0].total_questions;
                    };
                    break;
                case 'tag':
                    if(!a) {
                        too_few_args = 1;
                        break;
                    }
                    url = ['tags/' + a + '/wikis', ''];
                    extractor = function(data) {
                        if(data.items.length == 0) {
                            return 'Tag not found';
                        }
                        return data.items[0].excerpt;
                    };
                    break;
                case 'featured':
                    url = s.indexOf('meta.') == 0? ['questions', '&tagged=featured']: ['questions/featured', ''];
                    extractor = function(data) {
                        var l = data.items.length;
                        return (data.items.has_more? 'more than ': '') + l;
                    }
                    break;
            }
            if(too_few_args) {
                write(x, 'You have not provided enough arguments');
            } else if(!url) {
                write(x, 'The command ' + c + ' does not exist');
            } else {
                $.get('http://api.stackexchange.com/2.2/' + url[0] + site + url[1], function(data) {
                    write(x, extractor(data));
                });
            }
        });

        function write(x, m) {
            $('#input').val('@' + $(x).parents('.user-container').find('.username').eq(0).text() + ' ' + m);
            $('#sayit-button').click();
        }
    }
    new MutationObserver(update).observe($('#chat').get(0), {childList: true, subtree: true});
});
Tyilo
fuente
Bien, gracias por publicar una respuesta aquí. Ahora voy a hacer las pruebas en la sala. De todos modos, creo que puedes guardar algunos caracteres usando más variables de una letra y evitando updatey latestMessage.
ProgramFOX
¡Excelente! Has pasado todas las pruebas . Lo único extraño que noté fue que su bot devolvió un recuento de preguntas diferente al de la página de inicio, pero vi que la API devolvió ese número, así que informé esto en Meta y marqué el caso de prueba como correcto. ¡Bien hecho! +1
ProgramFOX
Te vi acortar el bot. ¡Agradable! :) Lo volví a probar y todavía pasas todas las pruebas.
ProgramFOX
Más bien tarde, pero acabo de encontrar una mejora de un carácter: puede reemplazar 0==e.items.lengthcon 1>e.items.lengthporque la longitud nunca será inferior a cero.
ProgramFOX