Pavel 2.0: consultor reptilóide em JS, node.js com soquetes e telefonia

Portanto, nosso INTERCOM'18 morreu, com preferência e casos de negócios. Como sempre, a entrada para a conferência foi paga: quem quisesse poderia comprar ingressos para o TimePad pelo preço total ou ... obter um desconto de um consultor reptiliano no site . No ano passado, funcionou como um retorno de chamada familiar: você deixa o telefone de forma especial, Pavel liga para você em um minuto e faz perguntas; quanto mais respostas corretas, maior o desconto. Desta vez, decidimos mudar a mecânica, tornando mais difícil tanto tecnicamente quanto em termos de questões. Sob o corte - as tripas do Pavlik 2.0, com o nó atual e os soquetes da Web, não se esqueça de usar macacão antes de abrir.


Mecânica do concurso


Você acessa o intercomconf.com em um navegador de desktop, no canto inferior direito, a Pavlik “acorda” na forma de um bate-papo e se oferece para jogar. Digite o número, clique em "Aqui está o meu número" - depois disso, Pavel levanta uma sessão entre o navegador e o back-end.



Se tudo tiver aumentado com sucesso e seu número ainda não tiver participado do sorteio, Paulo oferecerá o número 8-800. Aqui a nuvem Voximplant entra e o questionário começa:


Resposta: prazo / prazo. Baseado no meme Isso é bom .

Sim, os quebra-cabeças eram algo assim. Foram feitas três tentativas para cada pergunta: a princípio, havia uma imagem “complexa”, depois era mais simples e, no final, a mais simples. As primeiras tentativas deram mais pontos; após 5 quebra-cabeças, Pavel contou pontos e deu um ingresso grátis ou um desconto de 10% a 30%.

Ao mesmo tempo, nosso reptilóide é inteligente o suficiente: emitiu mensagens de erro (se você digitou o número de telefone incorretamente, por exemplo), determinou que o número já havia participado do desenho (“vejo um número familiar na tela do meu celular inexistente. Uma tentativa em uma mão. Essas são as regras. ") E, o mais importante, correlacionou o navegador e a nuvem. Como funcionou essa ousada URA?

Nos maxilares insanidade reptilóide



Resposta: central de atendimento. Nuff disse.

Para colocá-lo seco, Paul 2.0 é um URA em execução em nossa nuvem. Portanto, toda a lógica reptilóide deve ser explicitada em um script JS, certo? Sim mas não

A segunda versão do Pavel é sincronizada com o navegador do cliente: no site, Pavel mostra abusos e o telefone ouve suas respostas, dependendo da alteração das imagens e do resultado exibido. À primeira vista, essa interação pode ser implementada usando nossa API HTTP :

  • primeiro, o navegador executaria o script usando o método StartScenarios . Na resposta, o método retorna os parâmetros media_session_access_url e media_session_access_secure_url que contêm URLs para HTTP e HTTPS, respectivamente;
  • pode-se comunicar com o script em execução usando os URLs recebidos;
  • o script informa ao navegador quais imagens usar e atualiza a pontuação usando o método httpRequestAsync .

Mas como "pegar" um navegador personalizado? De fato, no httpRequestAsync você precisa passar um URL exclusivo. E sim, fotos - elas também precisam ser armazenadas em algum lugar.

Portanto, além do script JS da nuvem, usamos nosso backend no express.js emparelhado com socket.io : quando o visitante digitou o número, o navegador deu esse número ao back-end via http, após o qual a sessão http se transformou em uma sessão nos soquetes da web. Como resultado, o script se comunicava constantemente com o back-end via http, e o back-end já usava soquetes da web para exibir rapidamente imagens e pontos calculados no navegador.

No lado dos soquetes da web, o backend era assim:
'use strict';
const express = require('express');
const request = require('request');
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io')(http).listen(server);
var session = require('express-session')({
secret: 'secret',
resave: true,
saveUninitialized: true
});
var sharedsession = require('express-socket.io-session');
var sockets = {};
var PORT = process.env.PORT || 3001;
app.use(session);
io.use(sharedsession(session));
io.on('connection', function (socket) {
if (socket[socket.handshake.session.caller_id] === undefined &&
socket.handshake.session.caller_id !== undefined) {
sockets[socket.handshake.session.caller_id] = socket
}
});
app.use((req, res, next) => {
let allowedOrigins = [
// allowed hosts
];
let origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS');
res.header('Access-Control-Allow-Credentials', true);
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
})
view raw pavel-backend.js hosted with ❤ by GitHub

Mas ainda assim, a maior parte da lógica foi armazenada no script. Considere um reptilóide deste lado ...

Siga o script



Resposta: aprendizado de máquina. Retirado do Instagram, o próprio Arnie .

Do óbvio: você definitivamente precisa conectar o módulo de reconhecimento ASR .

require(Modules.ASR); 

Do interessante:

  • havia um objeto de perguntas no script com todas as respostas e nomes de arquivos .jpg ;
    sempre que o script foi executado, as perguntas foram embaralhadas usando a função auxiliar de reprodução aleatória :

    mostrar código
    function shuffle(a) {
    var j, x, i;
    for (i = a.length - 1; i > 0; i--) {
    j = Math.floor(Math.random() * (i + 1));
    x = a[i];
    a[i] = a[j];
    a[j] = x;
    }
    return a;
    }
    view raw shuffle.js hosted with ❤ by GitHub
  • Um manipulador de "nível superior" para uma chamada recebida ( CallAlerting ) verifica a exclusividade do telefone e também contém manipuladores para conectar e encerrar uma chamada. Dentro do onCallConnected há uma chamada para o back-end (leia, para o socketio):

    mostrar código
    VoxEngine.addEventListener(AppEvents.CallAlerting, async (e) => {
    call.addEventListener(CallEvents.Connected, onCallConnected);
    call.addEventListener(CallEvents.Disconnected, onCallDisconnected);
    // ...
    })
    function onCallConnected(e) {
    call.say(" , ! : , , <say-as stress='2'></say-as>." +
    " , . . ??? !",
    Language.RU_RUSSIAN_MALE);
    call.addEventListener(CallEvents.PlaybackFinished, startGame);
    call.record({
    stereo: true
    });
    call.addEventListener(CallEvents.RecordStarted, async (rec) => {
    let res = await Net.httpRequestAsync(ws + '/urlResult?caller_id=' + encodeURIComponent(caller_id) + '&url=' +
    encodeURIComponent(rec.url))
    });
    }
  • logo acima do startGame é visível , apenas as perguntas são misturadas, cortadas e enviadas para o back-end junto com os índices de imagem:

    mostrar código
    async function startGame() {
    call.removeEventListener(CallEvents.PlaybackFinished);
    shuffle(questions);
    questions = questions.slice(0, 5);
    let res = await Net.httpRequestAsync(ws + '/voxResult?caller_id=' + encodeURIComponent(caller_id) + '&data=' +
    encodeURIComponent(JSON.stringify({
    action: "start",
    // qIndex attempts = 0
    data: questions[qIndex].pics[attempts],
    points: points
    })));
    try {
    res = JSON.parse(res.text);
    } catch (err) {
    Logger.write(err);
    }
    if (res.result === true) {
    Logger.write("===--- The Game has started! ---===");
    startASR(); //
    wireCall(); // ASR
    }
    }
    view raw startGame.js hosted with ❤ by GitHub
  • O startASR cria uma instância ASR e indica o dicionário de reconhecimento preferido. Quando o jogador fala a resposta, a função interrompe o ASR e começa a processar o resultado ouvido no reconhecimento ;
  • onRecognitionResult remove o excesso da resposta:

     let rr = e[0].replace(" ", "").replace(" ", "").replace("  ", "").replace("  ", ""); rr = rr.replace(/ /g, ''); 

    E então começa a contar tentativas, pontos e também comentários de vozes ao longo do caminho:

    mostrar código
    let found = questions[qIndex].answers.some(r => rr.indexOf(r) >= 0);
    Logger.write("FOUND: " + found);
    if (found) {
    if (attempts == 0) {
    points += 5;
    call.say("<say-as stress='3'></say-as>! !", Language.RU_RUSSIAN_MALE);
    } else if (attempts == 1) {
    points += 3;
    call.say("! .", Language.RU_RUSSIAN_MALE);
    } else if (attempts == 2) {
    points += 1;
    call.say(" … . .", Language.RU_RUSSIAN_MALE);
    }

    A função também incrementa as variáveis ​​com tentativas e o número da pergunta para mudar para a próxima pergunta ou terminar o jogo;
  • a função final gameFinished fornece ao back-end a quantidade de pontos se uma pessoa ganhou um código promocional - isso pode ser visto no navegador e ouvido no telefone, porque Pavlik dá voz à vitória; depois que o hangup estiver pronto.

A lista geral do script se aproxima de 300 linhas, a parte mais volumosa é o processamento do resultado do reconhecimento, no resultado de reconhecimento.

Falando de fóssil



Resposta: Firefox. Nós temos tudo.

Pavel é um dinossauro, mas se mantém atualizado: se desenvolve ano após ano e ainda gosta de brincar. Esperamos que você tenha classificado a segunda versão do nosso reptilóide ao vivo e em termos de implementação. Compartilhe suas opiniões nos comentários, seja saudável e lembre-se - Paul te ama!

Source: https://habr.com/ru/post/pt426417/


All Articles