Crie um chatbot para as salas de chat do Stack Exchange


39

O desafio

O objetivo deste desafio é criar um chatbot que possa ser executado nas salas de chat do Stack Exchange. Seu bot precisa ser capaz de detectar quando comandos específicos são postados por um usuário e responder a ele. Esta é a lista de comandos e o que seu bot deve fazer:

  • !!newest: gera o título (sem link, mas o título) da pergunta mais recente publicada neste site (codegolf.SE).
  • !!metanewest: gera o título da pergunta mais recente publicada no site meta (meta.codegolf.SE).
  • !!questioncount: gera a contagem atual de perguntas.
  • !!metaquestioncount: gera a contagem de perguntas atual no meta site.
  • !!tag tagname: gera o trecho da tag (a breve descrição) da tag fornecida como o primeiro parâmetro.
  • !!metatag tagname: o mesmo que acima, mas para o meta site.
  • !!featured: gera a contagem de perguntas que atualmente possuem uma recompensa.
  • !!metafeatured: gera a contagem de perguntas que possuem a tag [featured] no Meta.

Regras

  1. Você deve escrever um programa completo, não um trecho ou função.
  2. Caso seja necessário, você pode solicitar nome de usuário e senha como entrada (solicitando entrada, STDIN, argumentos da linha de comando). Isso será necessário se você usar, por exemplo, Python ou Ruby, mas não será necessário se você usar JavaScript e executar o script na própria página da sala de bate-papo.
  3. Você tem permissão para usar bibliotecas externas para fazer coisas como WebSockets. Essas bibliotecas não precisam contar para a contagem de caracteres.
  4. Você pode usar um invólucro de bate-papo externo (mas não é necessário, escrever o seu próprio é incentivado) e, em seguida, é necessário contar a contagem de caracteres. Você também não tem permissão para alterar o código do wrapper. Se você usá-lo, use-o sem modificações e todos os caracteres deverão ser contados (isso é uma penalidade por não escrever seu próprio invólucro).

    Somente o código do wrapper em si precisa contar. Se houver outros arquivos, como exemplos, eles não precisam contar.

  5. Não há uso de encurtadores de URL ou de outras maneiras que os tornem mais curtos: o desafio é jogar no chatbot, não jogar no URL.
  6. Não há solicitações da web, exceto as necessárias para conversar e obter as informações necessárias para responder aos comandos.
  7. O uso das "brechas" padrão não é permitido.
  8. Se alguém postar um comando, você precisa responder com uma mensagem de chat deste formato: @user response. Então, se eu escrever o comando !!featurede houver 5 perguntas em destaque, seu bot deverá postar @ProgramFOX 5.
  9. Se eu testar seu bot, executarei na minha conta do chatbot e nesta sala de bate-papo . Sempre testarei os bots nessa sala, portanto, não é necessário fornecer o ID da sala como entrada, sempre será 14697. Esse ID não será fornecido como entrada, deve ser codificado.
  10. Se o comando não for encontrado, produza @user The command [command] does not exist. Substitua [command]pelo nome do comando não existente. Se argumentos forem fornecidos ao comando, não os produza, apenas o nome do comando.
  11. Se um comando tiver muitos argumentos, ignore os argumentos que não são necessários.
  12. Se um comando não tiver argumentos suficientes, produza @user You have not provided enough arguments
  13. O sistema impede que mensagens duplicadas sejam postadas dentro de um curto período de tempo. Portanto, ao testar seu bot, nunca executarei dois comandos que fornecem a mesma saída sucessivamente (o que significa que você não precisa implementar um sistema que torne as mensagens diferentes se forem duplicadas, adicionando um ponto, por exemplo).
  14. O sistema evita que muitas mensagens sejam postadas em um curto intervalo de tempo; portanto, ao testar, nunca enviarei muitos comandos em um curto intervalo de tempo, o que significa que seu bot não precisa cuidar disso (aguardando algum tempo) antes de postar, por exemplo).
  15. Isso é , o programa com a menor quantidade de bytes vence.

Começando

Aqui estão algumas informações para começar a escrever seu bot. Você não precisa usar isso, mas pode ser uma orientação.

  • Para fazer login, primeiro faça login em um provedor OpenID. Sempre será OpenID da pilha (Exchange Exchange https://openid.stackexchange.com). O formulário de login está localizado em https://openid.stackexchange.com/account/logine fornece o nome de usuário e a senha.
  • Em seguida, faça o login em stackexchange.com. O formulário de login está localizado em https://stackexchange.com/users/login. Escolha Stack Exchange como provedor OpenID.
  • Depois de fazer isso, faça login para conversar. O formulário de login para isso está localizado em http://stackexchange.com/users/chat-login. Escolha Stack Exchange como provedor OpenID.
  • Então você precisa obter o seu fkey. Para isso, acesse http://chat.stackexchange.com/chats/join/favoritee obtenha o fkeyde um campo de entrada oculto.
  • Para postar uma mensagem, envie uma solicitação http://chat.stackexchange.com/chats/14697/messages/newe forneça dois parâmetros POST: um textparâmetro que contém o texto da mensagem e um fkeyparâmetro que contém o fkey.
  • Para ver quando uma nova mensagem é postada, você pode usar o WebSockets (mas não precisa, fique à vontade para usar outra coisa, se for mais curta). Por favor, veja esta resposta do Meta Stack Exchange :

    Bate-papo

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

    O hash pode ser buscado postando o ID da sala e fkey para http://chat.stackexchange.com/ws-auth

    O timethingy é a chave de tempo do json retornado por /chats/<roomno>/events.

    O ID do evento quando uma mensagem é publicada é 1.

  • É útil examinar os wrappers de bate-papo existentes, como StackExchange-Chatty da Doorknob e ChatExchange da Manishearth , para ver como ele funciona exatamente.

3
No momento em que vi o título, pensei instantaneamente "ah, ProgramFOX".
26/06

Eu estava esperando que metafeaturedsignificaria perguntas bountied na meta, mas ... obrigado :-)
John Dvorak

@JanDvorak As metas por site não têm recompensas, então não posso usá-las. Quando escrevi esse desafio, esqueci que o Meta tinha uma tag [em destaque], então obrigado pela sua sugestão!
precisa

O que eu fiz para ver se uma nova mensagem foi enviada foi verificar a cada 2 segundos via JS se a última mensagem não foi por mim (último item em sala de aula)
Cilan

Já temos um aqui
Sr. Alien

Respostas:


14

JavaScript + jQuery, 1362 1258 bytes

Jogou golfe usando um minifier:

$(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})})

Você precisa executar o script diretamente no navegador (usando o jQuery do Stack Exchange):

  1. Abra http://chat.stackexchange.com/rooms/14697/chatbot-challenge-on-programming-puzzles-code-golf
  2. Cole o código acima no console
  3. Digite alguns comandos no chat

Poderia ser jogado muito mais, mas não poderia ser incomodado.


Sem golfe:

$(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});
});

Bom, obrigado por postar uma resposta aqui! Agora vou fazer os testes na sala. Enfim, acho que você pode salvar alguns caracteres usando mais variáveis ​​de uma letra e evitando updatee latestMessage.
precisa

Ótimo! Você passou em todos os testes . A única coisa estranha que notei foi que seu bot retornou uma contagem de perguntas diferente da página inicial, mas vi que a API retornou esse número, então relatei isso no Meta e marquei o caso de teste como correto. Bem feito! 1
ProgramFOX

Vi que você diminuiu o bot. Agradável! :) Eu re-testei e você ainda passa em todos os testes.
precisa

Um pouco tarde, mas acabei de encontrar uma melhoria de um caractere: você pode substituir 0==e.items.lengthpor, 1>e.items.lengthporque o comprimento nunca ficará abaixo de zero.
ProgramFOX
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.