Pavel 2.0: consultor reptiloide en JS, node.js con sockets y telefonía

Entonces nuestro INTERCOM'18 se calmó, con preferencia y casos de negocios. Como de costumbre, la entrada a la conferencia fue pagada: aquellos que quisieran podrían comprar boletos para TimePad al precio completo, u ... obtener un descuento de un consultor reptiliano en el sitio . El año pasado, funcionó como una devolución de llamada familiar: dejas el teléfono en una forma especial, Pavel te llama en un minuto y hace preguntas; Cuantas más respuestas correctas, mayor será el descuento. Esta vez decidimos cambiar la mecánica, haciéndolo más difícil tanto técnicamente como en términos de problemas. Debajo del corte: las agallas Pavlik 2.0, con el nodo actual y los zócalos web, no olviden ponerse un mono antes de abrir.


Mecánica del concurso


Accede a intercomconf.com desde un navegador de escritorio, en la esquina inferior derecha Pavlik "se despierta" en forma de chat y ofrece jugar el juego. Ingrese el número, haga clic en "Aquí está mi número". Después de esto, Pavel levanta una sesión entre su navegador y nuestro servidor.



Si todo ha aumentado con éxito y su número aún no ha participado en el sorteo, entonces Paul ofrecerá llamar al 8-800. Aquí entra la nube de Voximplant y comienza la prueba:


Respuesta: plazo / plazo. Basado en el meme Esto está bien .

Sí, los acertijos eran algo así. Se hicieron tres intentos para cada pregunta: al principio había una imagen "compleja", luego era más simple y al final la más simple. Los primeros intentos dieron la mayor cantidad de puntos; Después de 5 rompecabezas, Pavel contó puntos y dio un boleto gratis o un descuento del 10% al 30%.

Al mismo tiempo, nuestro reptiloide es lo suficientemente inteligente: emitió mensajes de error (si ingresó el número de teléfono incorrectamente, por ejemplo), determinó que el número ya había participado en el sorteo ("Veo un número familiar en la pantalla de mi teléfono móvil inexistente. Un intento en una mano: estas son las reglas. ") Y, lo más importante, correlacionó el navegador y la nube. ¿Cómo funcionó este atrevido IVR?

En las fauces locura reptiloide



Respuesta: centro de llamadas. Dijo Nuff.

Para decirlo en seco, Paul 2.0 es un IVR que se ejecuta en nuestra nube. Por lo tanto, toda la lógica reptiloide debe estar enunciada en un script JS, ¿verdad? Si pero no.

La segunda versión de Pavel se sincroniza con el navegador del cliente: en el sitio, Pavel muestra rechazos, y en el teléfono escucha sus respuestas, según las imágenes que cambien y se muestre el resultado. A primera vista, esta interacción podría implementarse utilizando nuestra API HTTP :

  • primero, el navegador ejecutaría el script usando el método StartScenarios . En la respuesta, el método devuelve los parámetros media_session_access_url y media_session_access_secure_url que contienen las URL para HTTP y HTTPS, respectivamente;
  • uno podría comunicarse con el script en ejecución utilizando las URL recibidas;
  • el script le dirá al navegador qué imágenes usar y actualizará el puntaje usando el método httpRequestAsync .

Pero, ¿cómo "atrapar" un navegador personalizado? De hecho, en httpRequestAsync necesita pasar una URL única. Y sí, imágenes: también deben almacenarse en algún lugar.

Por lo tanto, además del script JS en la nube, utilizamos nuestro backend en express.js emparejado con socket.io : cuando el visitante ingresó el número, el navegador le dio este número al backend a través de http, luego de lo cual la sesión http se convirtió en una sesión en sockets web. Como resultado, el script se comunicaba constantemente con el backend a través de http, y el backend ya usaba sockets web para lanzar rápidamente imágenes y puntos calculados al navegador.

En el lado de los zócalos web, el backend se veía así:
'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

Pero aún así, la mayor parte de la lógica se almacenó en el script. Considere un reptiloide de este lado ...

Sigue el guión



Respuesta: aprendizaje automático. Tomado del propio Instagram Arnie .

De lo obvio: definitivamente necesita conectar el módulo de reconocimiento ASR .

require(Modules.ASR); 

De lo interesante:

  • había un objeto de preguntas en el script con todas las respuestas y nombres de archivo .jpg ;
    Cada vez que se ejecutaba el script, las preguntas se barajaban utilizando la función de ayuda aleatoria :

    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
  • Un controlador de "nivel superior" para una llamada entrante ( CallAlerting ) verifica la singularidad del teléfono y también contiene controladores para conectar y finalizar una llamada. Justo dentro de onCallConnected hay una llamada al backend (leer, a 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))
    });
    }
  • justo encima de startGame es visible , en él solo se mezclan las preguntas, se cortan y se envían al backend junto con los índices de imagen:

    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
  • startASR crea una instancia de ASR e indica el diccionario de reconocimiento preferido. Cuando el jugador dice la respuesta, la función detiene el ASR y comienza a procesar el escucha - en ReconocimientoResultado ;
  • onRecognitionResult elimina el exceso de la respuesta:

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

    Y luego comienza a contar intentos, puntos y también expresa comentarios en el camino:

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

    La función también incrementa las variables con intentos y el número de pregunta para cambiar a la siguiente pregunta o finalizar el juego;
  • La función final de GameFinished le da al backend la cantidad de puntos si una persona ganó un código promocional; esto se puede ver en el navegador y escuchar en el teléfono, porque Pavlik expresa la victoria; después de que se realiza la suspensión .

La lista general de la secuencia de comandos se aproxima a 300 líneas, la pieza más voluminosa es el procesamiento del resultado de reconocimiento, en ReconocimientoResultado .

Fósil parlante



Respuesta: Firefox Lo tenemos todo

Pavel es un dinosaurio, pero se mantiene actualizado: se desarrolla año tras año y todavía le gusta bromear. Esperamos que haya calificado la segunda versión de nuestro reptiloide tanto en vivo como en términos de implementación. Comparta sus opiniones en los comentarios, sea saludable y recuerde: ¡Paul lo ama!

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


All Articles