mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
9 Commits
v0.8.5-3
...
fix/nuance
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b600178ff | ||
|
|
7d355f2fac | ||
|
|
c6b8ec1b28 | ||
|
|
10159a0ba6 | ||
|
|
7a558c7349 | ||
|
|
4dbe7af9db | ||
|
|
4ec34faa29 | ||
|
|
e2d6086f9f | ||
|
|
0e056ad296 |
2
app.js
2
app.js
@@ -53,7 +53,7 @@ const {
|
||||
getTtsSize,
|
||||
purgeTtsCache,
|
||||
synthAudio
|
||||
} = require('@jambonz/speech-utils')({redis_client: client}, logger);
|
||||
} = require('@jambonz/speech-utils')({}, logger);
|
||||
const {
|
||||
lookupAppBySid,
|
||||
lookupAccountBySid,
|
||||
|
||||
@@ -11,7 +11,7 @@ class S3MultipartUploadStream extends Writable {
|
||||
super(opts);
|
||||
this.logger = logger;
|
||||
this.bucketName = opts.bucketName;
|
||||
this.objectKey = opts.objectKey;
|
||||
this.objectKey = opts.Key;
|
||||
this.uploadId = null;
|
||||
this.partNumber = 1;
|
||||
this.multipartETags = [];
|
||||
|
||||
@@ -5,7 +5,7 @@ const S3MultipartUploadStream = require('./s3-multipart-upload-stream');
|
||||
const getUploader = (key, metadata, bucket_credential, logger) => {
|
||||
const uploaderOpts = {
|
||||
bucketName: bucket_credential.name,
|
||||
objectKey: key,
|
||||
Key: key,
|
||||
metadata
|
||||
};
|
||||
try {
|
||||
|
||||
@@ -155,6 +155,9 @@ router.get('/:sid/RegisteredSipUsers', async(req, res) => {
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
|
||||
}
|
||||
if (!result[0].sip_realm) {
|
||||
throw new DbErrorBadRequest('account does not have sip_realm configuration');
|
||||
}
|
||||
const users = await registrar.getRegisteredUsersForRealm(result[0].sip_realm);
|
||||
res.status(200).json(users.map((u) => `${u}@${result[0].sip_realm}`));
|
||||
} catch (err) {
|
||||
@@ -162,6 +165,42 @@ router.get('/:sid/RegisteredSipUsers', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:sid/RegisteredSipUsers', async(req, res) => {
|
||||
const {logger, registrar} = req.app.locals;
|
||||
const users = req.body;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const result = await Account.retrieve(account_sid);
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
|
||||
}
|
||||
if (!result[0].sip_realm) {
|
||||
throw new DbErrorBadRequest('account does not have sip_realm configuration');
|
||||
}
|
||||
if (!users || !Array.isArray(users) || users.length === 0) {
|
||||
return res.status(200).json(await registrar.getRegisteredUsersDetailsForRealm(result[0].sip_realm));
|
||||
}
|
||||
const ret = [];
|
||||
for (const u of users) {
|
||||
const user = await registrar.query(`${u}@${result[0].sip_realm}`) || {
|
||||
name: u,
|
||||
contact: null,
|
||||
expiryTime: 0,
|
||||
protocol: null
|
||||
};
|
||||
ret.push({
|
||||
name: u,
|
||||
...user,
|
||||
registered_status: user.expiryTime > 0 ? 'active' : 'inactive',
|
||||
});
|
||||
}
|
||||
res.status(200).json(ret);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid/RegisteredSipUsers/:client', async(req, res) => {
|
||||
const {logger, registrar, lookupClientByAccountAndUsername} = req.app.locals;
|
||||
const client = req.params.client;
|
||||
@@ -225,7 +264,8 @@ function validateUpdateCall(opts) {
|
||||
'mute_status',
|
||||
'sip_request',
|
||||
'record',
|
||||
'tag'
|
||||
'tag',
|
||||
'dtmf'
|
||||
]
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
@@ -267,6 +307,9 @@ function validateUpdateCall(opts) {
|
||||
if (opts.record && !opts.record.action) {
|
||||
throw new DbErrorBadRequest('record requires action property');
|
||||
}
|
||||
if (opts.dtmf && !opts.dtmf.digit) {
|
||||
throw new DbErrorBadRequest('invalid dtmf');
|
||||
}
|
||||
if ('startCallRecording' === opts.record?.action && !opts.record.siprecServerURL) {
|
||||
throw new DbErrorBadRequest('record requires siprecServerURL property when starting recording');
|
||||
}
|
||||
|
||||
@@ -132,7 +132,8 @@ const encryptCredential = (obj) => {
|
||||
custom_tts_url,
|
||||
auth_token = '',
|
||||
cobalt_server_uri,
|
||||
model_id
|
||||
model_id,
|
||||
options
|
||||
} = obj;
|
||||
|
||||
switch (vendor) {
|
||||
@@ -210,7 +211,7 @@ const encryptCredential = (obj) => {
|
||||
case 'elevenlabs':
|
||||
assert(api_key, 'invalid elevenLabs speech credential: api_key is required');
|
||||
assert(model_id, 'invalid elevenLabs speech credential: model_id is required');
|
||||
const elevenlabsData = JSON.stringify({api_key, model_id});
|
||||
const elevenlabsData = JSON.stringify({api_key, model_id, options});
|
||||
return encrypt(elevenlabsData);
|
||||
|
||||
case 'assemblyai':
|
||||
@@ -411,7 +412,8 @@ router.put('/:sid', async(req, res) => {
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
cobalt_server_uri,
|
||||
model_id
|
||||
model_id,
|
||||
options
|
||||
} = req.body;
|
||||
|
||||
const newCred = {
|
||||
@@ -433,7 +435,8 @@ router.put('/:sid', async(req, res) => {
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
cobalt_server_uri,
|
||||
model_id
|
||||
model_id,
|
||||
options
|
||||
};
|
||||
logger.info({o, newCred}, 'updating speech credential with this new credential');
|
||||
obj.credential = encryptCredential(newCred);
|
||||
@@ -553,7 +556,7 @@ router.get('/:sid/test', async(req, res) => {
|
||||
} = credential;
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testMicrosoftTts(logger, {
|
||||
await testMicrosoftTts(logger, synthAudio, {
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
|
||||
@@ -4192,6 +4192,27 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve online sip users for an account by list of sip username
|
||||
operationId: listRegisteredSipUsers
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: retrieve online sip users for an account
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RegisteredClient'
|
||||
/Accounts/{AccountSid}/RegisteredSipUsers/{Client}:
|
||||
parameters:
|
||||
- name: Client
|
||||
|
||||
@@ -95,6 +95,13 @@ const testMicrosoftStt = async(logger, credentials) => {
|
||||
const speechConfig = sdk.SpeechConfig.fromSubscription(api_key, region);
|
||||
const audioConfig = sdk.AudioConfig.fromWavFileInput(fs.readFileSync(`${__dirname}/../../data/test_audio.wav`));
|
||||
speechConfig.speechRecognitionLanguage = 'en-US';
|
||||
|
||||
if (process.env.JAMBONES_HTTP_PROXY_IP && process.env.JAMBONES_HTTP_PROXY_PORT) {
|
||||
logger.debug(
|
||||
`testMicrosoftStt: using proxy ${process.env.JAMBONES_HTTP_PROXY_IP}:${process.env.JAMBONES_HTTP_PROXY_PORT}`);
|
||||
speechConfig.setProxy(process.env.JAMBONES_HTTP_PROXY_IP, process.env.JAMBONES_HTTP_PROXY_PORT);
|
||||
}
|
||||
|
||||
const speechRecognizer = new sdk.SpeechRecognizer(speechConfig, audioConfig);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -150,38 +157,19 @@ const testAwsStt = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testMicrosoftTts = async(logger, credentials) => {
|
||||
const {
|
||||
api_key,
|
||||
region,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
use_custom_tts,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
custom_tts_endpoint,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
use_custom_stt,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
custom_stt_endpoint
|
||||
} = credentials;
|
||||
|
||||
logger.info({
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
}, 'testing microsoft tts');
|
||||
if (!api_key) throw new Error('testMicrosoftTts: credentials are missing api_key');
|
||||
if (!region) throw new Error('testMicrosoftTts: credentials are missing region');
|
||||
const testMicrosoftTts = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
const getJSON = bent('json', {
|
||||
'Ocp-Apim-Subscription-Key': api_key
|
||||
});
|
||||
const response = await getJSON(`https://${region}.tts.speech.microsoft.com/cognitiveservices/voices/list`);
|
||||
return response;
|
||||
await synthAudio({increment: () => {}, histogram: () => {}},
|
||||
{
|
||||
vendor: 'microsoft',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'en-US-JennyMultilingualNeural',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({err}, `testMicrosoftTts - failed to list voices for region ${region}`);
|
||||
logger.info({err}, 'testMicrosoftTts returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
@@ -370,6 +358,8 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.secret = o.secret ? (isObscureKey ? obscureKey(o.secret) : o.secret) : null;
|
||||
obj.nuance_tts_uri = o.nuance_tts_uri;
|
||||
obj.nuance_stt_uri = o.nuance_stt_uri;
|
||||
}
|
||||
else if ('deepgram' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
@@ -395,6 +385,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.model_id = o.model_id;
|
||||
obj.options = o.options;
|
||||
} else if (obj.vendor.startsWith('custom:')) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.auth_token = isObscureKey ? obscureKey(o.auth_token) : o.auth_token;
|
||||
|
||||
2357
package-lock.json
generated
2357
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,9 +27,9 @@
|
||||
"@google-cloud/storage": "^6.12.0",
|
||||
"@jambonz/db-helpers": "^0.9.1",
|
||||
"@jambonz/lamejs": "^1.2.2",
|
||||
"@jambonz/mw-registrar": "^0.2.5",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/speech-utils": "^0.0.26",
|
||||
"@jambonz/speech-utils": "^0.0.32",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.45",
|
||||
"@soniox/soniox-node": "^1.1.1",
|
||||
|
||||
@@ -85,6 +85,24 @@ test('client test', async(t) => {
|
||||
t.ok(result.length === 1 && result[0] === 'dhorton@drachtio.org',
|
||||
'successfully queried all registered clients');
|
||||
|
||||
result = await request.post(`/Accounts/${account_sid}/RegisteredSipUsers`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: ['dhorton']
|
||||
});
|
||||
t.ok(result.body.length === 1 && result.body[0].name === 'dhorton',
|
||||
'successfully queried all registered clients');
|
||||
|
||||
result = await request.post(`/Accounts/${account_sid}/RegisteredSipUsers`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: []
|
||||
});
|
||||
t.ok(result.body.length === 1 && result.body[0].name === 'dhorton',
|
||||
'successfully queried all registered clients');
|
||||
|
||||
/* query all entity */
|
||||
result = await request.get('/Clients', {
|
||||
auth: authAdmin,
|
||||
|
||||
Reference in New Issue
Block a user