lib/wit.js (166 lines of code) (raw):

/** * Copyright (c) Meta Platforms, Inc. and its affiliates. All Rights Reserved. */ 'use strict'; const {DEFAULT_API_VERSION, DEFAULT_WIT_URL} = require('./config'); const log = require('./log'); const fetch = require('isomorphic-fetch'); const EventEmitter = require('events'); const HttpsProxyAgent = require('https-proxy-agent'); const {Readable} = require('stream'); const Url = require('url'); class Wit extends EventEmitter { constructor(opts) { super(); this.config = Object.freeze(validate(opts)); } message(q, context, n) { if (typeof q !== 'string') { throw new Error('Please provide a text input (string).'); } const {apiVersion, headers, logger, proxy, witURL} = this.config; const params = { q, v: apiVersion, }; if (typeof context === 'object') { params.context = JSON.stringify(context); } if (typeof n === 'number') { params.n = JSON.stringify(n); } const method = 'GET'; const fullURL = witURL + '/message?' + encodeURIParams(params); logger.debug(method, fullURL); return fetch(fullURL, { method, headers, proxy, }) .then(response => Promise.all([response.json(), response.status])) .then(makeWitResponseHandler(logger, 'message')); } speech(contentType, body, context, n) { if (typeof contentType !== 'string') { throw new Error('Please provide a content-type (string).'); } if (!body instanceof Readable) { throw new Error('Please provide an audio stream (Readable).'); } const {apiVersion, headers, logger, proxy, witURL} = this.config; const params = { v: apiVersion, }; if (typeof context === 'object') { params.context = JSON.stringify(context); } if (typeof n === 'number') { params.n = JSON.stringify(n); } const method = 'POST'; const fullURL = witURL + '/speech?' + encodeURIParams(params); logger.debug(method, fullURL); const req = fetch(fullURL, { body, method, proxy, headers: { ...headers, 'Content-Type': contentType, 'Transfer-Encoding': 'chunked', }, }); const _partialResponses = req .then( response => new Promise((resolve, reject) => { logger.debug('status', response.status); const bodyStream = response.body; bodyStream.on('readable', () => { let chunk; let contents = ''; while (null !== (chunk = bodyStream.read())) { contents += chunk.toString(); } for (const {error, intents, text} of splitHttpChunks( contents, ).map(x => JSON.parse(x))) { if (!(error || intents)) { logger.debug('[speech] partialTranscription:', text); this.emit('partialTranscription', text); } else if (text) { logger.debug('[speech] fullTranscription:', text); this.emit('fullTranscription', text); } } }); }), ) .catch(e => logger.error('[speech] could not parse partial response', e)); return req .then(response => Promise.all([response.text(), response.status])) .then(([contents, status]) => [ JSON.parse(splitHttpChunks(contents).pop()), status, ]) .catch(e => e) .then(makeWitResponseHandler(logger, 'speech')); } } const makeWitResponseHandler = (logger, endpoint) => rsp => { const error = e => { logger.error('[' + endpoint + '] Error: ' + e); throw e; }; if (rsp instanceof Error) { return error(rsp); } const [json, status] = rsp; if (json instanceof Error) { return error(json); } const err = json.error || (status !== 200 && json.body + ' (' + status + ')'); if (err) { return error(err); } logger.debug('[' + endpoint + '] Response: ' + JSON.stringify(json)); return json; }; const getProxyAgent = witURL => { const url = Url.parse(witURL); const proxy = url.protocol === 'http:' ? process.env.http_proxy || process.env.HTTP_PROXY : process.env.https_proxy || process.env.HTTPS_PROXY; const noProxy = process.env.no_proxy || process.env.NO_PROXY; const shouldIgnore = noProxy && noProxy.indexOf(url.hostname) > -1; if (proxy && !shouldIgnore) { return new HttpsProxyAgent(proxy); } if (!proxy) { return null; } }; const encodeURIParams = params => Object.entries(params) .map(([key, value]) => key + '=' + encodeURIComponent(value)) .join('&'); const splitHttpChunks = response => response .split('\r\n') .map(x => x.trim()) .filter(x => x.length > 0); const validate = opts => { if (!opts.accessToken) { throw new Error( 'Could not find access token, learn more at https://wit.ai/docs', ); } opts.witURL = opts.witURL || DEFAULT_WIT_URL; opts.apiVersion = opts.apiVersion || DEFAULT_API_VERSION; opts.headers = opts.headers || { Authorization: 'Bearer ' + opts.accessToken, 'Content-Type': 'application/json', }; opts.logger = opts.logger || new log.Logger(log.INFO); opts.proxy = getProxyAgent(opts.witURL); return opts; }; module.exports = Wit;