mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
40 Commits
swagger-pa
...
v0.9.3-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c56d8ea0 | ||
|
|
9cfe990bb8 | ||
|
|
6c7d2c9074 | ||
|
|
73e35c84c5 | ||
|
|
86d50d94cb | ||
|
|
b8f4ad6b27 | ||
|
|
ad3ec926ee | ||
|
|
66bd9a442c | ||
|
|
fa81d179a1 | ||
|
|
fab8a391b7 | ||
|
|
89288acf6e | ||
|
|
23cd4408a5 | ||
|
|
ce4618523c | ||
|
|
0eb8097e32 | ||
|
|
8851b3fac0 | ||
|
|
e080118b6a | ||
|
|
75c27e3f80 | ||
|
|
843980c7f6 | ||
|
|
f9990da468 | ||
|
|
e8d5655abb | ||
|
|
e908f5830c | ||
|
|
5c7bac91a8 | ||
|
|
de250c8d58 | ||
|
|
84d83a0a48 | ||
|
|
b5bede7a08 | ||
|
|
6e779f6744 | ||
|
|
77b9ca4cba | ||
|
|
0451b6982c | ||
|
|
71adc577e9 | ||
|
|
e8b32103fe | ||
|
|
57d8d0a02c | ||
|
|
a41760fa9f | ||
|
|
c6bae80a03 | ||
|
|
4cddbd83a1 | ||
|
|
6275aac341 | ||
|
|
52de41c9bc | ||
|
|
ed71abd675 | ||
|
|
2d2b98dab5 | ||
|
|
7553e2b617 | ||
|
|
b921cab867 |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -7,11 +7,22 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
- run: npm install
|
||||
- run: npm run jslint
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
- run: npm test
|
||||
- run: npm run test:encrypt-decrypt
|
||||
|
||||
|
||||
|
||||
2
app.js
2
app.js
@@ -222,7 +222,7 @@ server.on('upgrade', (request, socket, head) => {
|
||||
|
||||
/* complete the upgrade */
|
||||
wsServer.handleUpgrade(request, socket, head, (ws) => {
|
||||
logger.info(`upgraded to websocket, url: ${request.url}`);
|
||||
logger.debug(`upgraded to websocket, url: ${request.url}`);
|
||||
wsServer.emit('connection', ws, request.url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -162,7 +162,7 @@ regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed num
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='An ordered list of digit patterns in an LCR table. The patterns are tested in sequence until one matches';
|
||||
) COMMENT='An ordered list of digit patterns in an LCR table. The pat';
|
||||
|
||||
CREATE TABLE lcr
|
||||
(
|
||||
@@ -173,7 +173,7 @@ default_carrier_set_entry_sid CHAR(36) COMMENT 'default carrier/route to use whe
|
||||
service_provider_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
PRIMARY KEY (lcr_sid)
|
||||
) COMMENT='An LCR (least cost routing) table that is used by a service provider or account to make decisions about routing outbound calls when multiple carriers are available.';
|
||||
) COMMENT='An LCR (least cost routing) table that is used by a service ';
|
||||
|
||||
CREATE TABLE password_settings
|
||||
(
|
||||
@@ -351,6 +351,8 @@ speech_credential_sid CHAR(36) NOT NULL,
|
||||
model VARCHAR(512) NOT NULL,
|
||||
reported_usage ENUM('REPORTED_USAGE_UNSPECIFIED','REALTIME','OFFLINE') DEFAULT 'REALTIME',
|
||||
name VARCHAR(64) NOT NULL,
|
||||
voice_cloning_key MEDIUMTEXT,
|
||||
use_voice_cloning_key BOOLEAN DEFAULT false,
|
||||
PRIMARY KEY (google_custom_voice_sid)
|
||||
);
|
||||
|
||||
@@ -358,7 +360,9 @@ CREATE TABLE system_information
|
||||
(
|
||||
domain_name VARCHAR(255),
|
||||
sip_domain_name VARCHAR(255),
|
||||
monitoring_domain_name VARCHAR(255)
|
||||
monitoring_domain_name VARCHAR(255),
|
||||
private_network_cidr VARCHAR(8192),
|
||||
log_level ENUM('info', 'debug') NOT NULL DEFAULT 'info'
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
@@ -412,6 +416,7 @@ register_from_user VARCHAR(128),
|
||||
register_from_domain VARCHAR(255),
|
||||
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
register_status VARCHAR(4096),
|
||||
dtmf_type ENUM('rfc2833','tones','info') NOT NULL DEFAULT 'rfc2833',
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
@@ -552,6 +557,7 @@ siprec_hook_sid CHAR(36),
|
||||
record_all_calls BOOLEAN NOT NULL DEFAULT false,
|
||||
record_format VARCHAR(16) NOT NULL DEFAULT 'mp3',
|
||||
bucket_credential VARCHAR(8192) COMMENT 'credential used to authenticate with storage service',
|
||||
enable_debug_log BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (account_sid)
|
||||
) COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -197,6 +197,18 @@ const sql = {
|
||||
'ALTER TABLE applications MODIFY COLUMN speech_synthesis_voice VARCHAR(256)',
|
||||
'ALTER TABLE applications MODIFY COLUMN fallback_speech_synthesis_voice VARCHAR(256)',
|
||||
'ALTER TABLE sip_gateways ADD COLUMN use_sips_scheme BOOLEAN NOT NULL DEFAULT 0',
|
||||
],
|
||||
9002: [
|
||||
'ALTER TABLE system_information ADD COLUMN private_network_cidr VARCHAR(8192)',
|
||||
'ALTER TABLE system_information ADD COLUMN log_level ENUM(\'info\', \'debug\') NOT NULL DEFAULT \'info\'',
|
||||
'ALTER TABLE accounts ADD COLUMN enable_debug_log BOOLEAN NOT NULL DEFAULT false',
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN use_voice_cloning_key BOOLEAN DEFAULT false',
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN voice_cloning_key MEDIUMTEXT',
|
||||
],
|
||||
9003: [
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN voice_cloning_key MEDIUMTEXT',
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN use_voice_cloning_key BOOLEAN DEFAULT false',
|
||||
'ALTER TABLE voip_carriers ADD COLUMN dtmf_type ENUM(\'rfc2833\',\'tones\',\'info\') NOT NULL DEFAULT \'rfc2833\'',
|
||||
]
|
||||
};
|
||||
|
||||
@@ -230,6 +242,8 @@ const doIt = async() => {
|
||||
if (val < 8004) upgrades.push(...sql['8004']);
|
||||
if (val < 8005) upgrades.push(...sql['8005']);
|
||||
if (val < 9000) upgrades.push(...sql['9000']);
|
||||
if (val < 9002) upgrades.push(...sql['9002']);
|
||||
if (val < 9003) upgrades.push(...sql['9003']);
|
||||
|
||||
// perform all upgrades
|
||||
logger.info({upgrades}, 'applying schema upgrades..');
|
||||
|
||||
@@ -56,9 +56,13 @@ AND effective_end_date IS NULL
|
||||
AND pending = 0`;
|
||||
|
||||
const extractBucketCredential = (obj) => {
|
||||
const {bucket_credential} = obj;
|
||||
if (bucket_credential) {
|
||||
obj.bucket_credential = JSON.parse(decrypt(bucket_credential));
|
||||
try {
|
||||
const {bucket_credential} = obj;
|
||||
if (bucket_credential) {
|
||||
obj.bucket_credential = JSON.parse(decrypt(bucket_credential));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while decrypting data', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -37,19 +37,24 @@ class Application extends Model {
|
||||
}
|
||||
|
||||
/**
|
||||
* list all applications - for all service providers, for one service provider, or for one account
|
||||
* list all applications - for all service providers, for one service provider, or for one account,
|
||||
* or by an optional name
|
||||
*/
|
||||
static retrieveAll(service_provider_sid, account_sid) {
|
||||
let sql = retrieveSql;
|
||||
static retrieveAll(service_provider_sid, account_sid, name) {
|
||||
let sql = retrieveSql + ' WHERE 1 = 1';
|
||||
const args = [];
|
||||
if (account_sid) {
|
||||
sql = `${sql} WHERE app.account_sid = ?`;
|
||||
sql = `${sql} AND app.account_sid = ?`;
|
||||
args.push(account_sid);
|
||||
}
|
||||
else if (service_provider_sid) {
|
||||
sql = `${sql} WHERE account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
sql = `${sql} AND account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
if (name) {
|
||||
sql = `${sql} AND app.name = ?`;
|
||||
args.push(name);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
@@ -13,9 +13,9 @@ class Client extends Model {
|
||||
}
|
||||
|
||||
static async retrieveAllByServiceProviderSid(service_provider_sid) {
|
||||
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.hashed_password
|
||||
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.password
|
||||
FROM ${this.table} AS c LEFT JOIN accounts AS acc ON c.account_sid = acc.account_sid
|
||||
LEFT JOIN service_providers AS sp ON sp.service_provider_sid = accs.service_provider_sid
|
||||
LEFT JOIN service_providers AS sp ON sp.service_provider_sid = acc.service_provider_sid
|
||||
WHERE sp.service_provider_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, service_provider_sid);
|
||||
return rows;
|
||||
|
||||
@@ -22,13 +22,17 @@ class SpeechCredential extends Model {
|
||||
|
||||
static async getSpeechCredentialsByVendorAndLabel(service_provider_sid, account_sid, vendor, label) {
|
||||
let sql;
|
||||
let rows = [];
|
||||
if (account_sid) {
|
||||
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ? ${label ? 'AND label = ?' : ''}`;
|
||||
} else {
|
||||
sql = `SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ?
|
||||
${label ? 'AND label = ?' : ''}`;
|
||||
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ?
|
||||
AND label ${label ? '= ?' : 'is NULL'}`;
|
||||
[rows] = await promisePool.query(sql, [account_sid, vendor, label]);
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
sql = `SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ?
|
||||
AND label ${label ? '= ?' : 'is NULL'}`;
|
||||
[rows] = await promisePool.query(sql, [service_provider_sid, vendor, label]);
|
||||
}
|
||||
const [rows] = await promisePool.query(sql, [account_sid ? account_sid : service_provider_sid, vendor, label]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ SystemInformation.fields = [
|
||||
name: 'monitoring_domain_name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'private_network_cidr',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = SystemInformation;
|
||||
|
||||
@@ -136,6 +136,10 @@ VoipCarrier.fields = [
|
||||
{
|
||||
name: 'register_status',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'dtmf_type',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -19,11 +19,12 @@ const {
|
||||
parseCallSid,
|
||||
enableSubspace,
|
||||
disableSubspace,
|
||||
parseVoipCarrierSid
|
||||
parseVoipCarrierSid,
|
||||
hasValue,
|
||||
} = require('./utils');
|
||||
const short = require('short-uuid');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const { encrypt } = require('../../utils/encrypt-decrypt');
|
||||
const { encrypt, obscureBucketCredentialsSensitiveData, isObscureKey } = require('../../utils/encrypt-decrypt');
|
||||
const { testS3Storage, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils');
|
||||
const translator = short();
|
||||
|
||||
@@ -42,7 +43,7 @@ const getFsUrl = async(logger, retrieveSet, setName) => {
|
||||
return ;
|
||||
}
|
||||
const f = fs[idx++ % fs.length];
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${f}`);
|
||||
logger.debug({fs}, `feature servers available for createCall API request, selecting ${f}`);
|
||||
return `${f}/v1/createCall`;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
|
||||
@@ -266,7 +267,8 @@ function validateUpdateCall(opts) {
|
||||
'record',
|
||||
'tag',
|
||||
'dtmf',
|
||||
'conferenceParticipantAction'
|
||||
'conferenceParticipantAction',
|
||||
'dub'
|
||||
]
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
@@ -554,9 +556,12 @@ router.get('/:sid', async(req, res) => {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const results = await Account.retrieve(account_sid, service_provider_sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
return res.status(200).json(results[0]);
|
||||
const [result] = await Account.retrieve(account_sid, service_provider_sid) || [];
|
||||
if (!result) return res.status(404).end();
|
||||
|
||||
result.bucket_credential = obscureBucketCredentialsSensitiveData(result.bucket_credential);
|
||||
|
||||
return res.status(200).json(result);
|
||||
}
|
||||
catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -638,19 +643,21 @@ router.delete('/:sid/SubspaceTeleport', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
function encryptBucketCredential(obj) {
|
||||
if (!obj.bucket_credential) return;
|
||||
function encryptBucketCredential(obj, storedCredentials = {}) {
|
||||
if (!hasValue(obj?.bucket_credential)) return;
|
||||
const {
|
||||
vendor,
|
||||
region,
|
||||
name,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
tags,
|
||||
service_key,
|
||||
connection_string,
|
||||
endpoint
|
||||
} = obj.bucket_credential;
|
||||
let {
|
||||
secret_access_key,
|
||||
service_key,
|
||||
connection_string
|
||||
} = obj.bucket_credential;
|
||||
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
@@ -658,6 +665,9 @@ function encryptBucketCredential(obj) {
|
||||
assert(secret_access_key, 'invalid aws S3 bucket credential: secret_access_key is required');
|
||||
assert(name, 'invalid aws bucket name: name is required');
|
||||
assert(region, 'invalid aws bucket region: region is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
secret_access_key = storedCredentials.secret_access_key;
|
||||
}
|
||||
const awsData = JSON.stringify({vendor, region, name, access_key_id,
|
||||
secret_access_key, tags});
|
||||
obj.bucket_credential = encrypt(awsData);
|
||||
@@ -667,18 +677,27 @@ function encryptBucketCredential(obj) {
|
||||
assert(secret_access_key, 'invalid aws S3 bucket credential: secret_access_key is required');
|
||||
assert(name, 'invalid aws bucket name: name is required');
|
||||
assert(endpoint, 'invalid endpoint uri: endpoint is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
secret_access_key = storedCredentials.secret_access_key;
|
||||
}
|
||||
const s3Data = JSON.stringify({vendor, endpoint, name, access_key_id,
|
||||
secret_access_key, tags});
|
||||
obj.bucket_credential = encrypt(s3Data);
|
||||
break;
|
||||
case 'google':
|
||||
assert(service_key, 'invalid google cloud storage credential: service_key is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
service_key = storedCredentials.service_key;
|
||||
}
|
||||
const googleData = JSON.stringify({vendor, name, service_key, tags});
|
||||
obj.bucket_credential = encrypt(googleData);
|
||||
break;
|
||||
case 'azure':
|
||||
assert(name, 'invalid azure container name: name is required');
|
||||
assert(connection_string, 'invalid azure cloud storage credential: connection_string is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
connection_string = storedCredentials.connection_string;
|
||||
}
|
||||
const azureData = JSON.stringify({vendor, name, connection_string, tags});
|
||||
obj.bucket_credential = encrypt(azureData);
|
||||
break;
|
||||
@@ -736,7 +755,17 @@ router.put('/:sid', async(req, res) => {
|
||||
delete obj.registration_hook;
|
||||
delete obj.queue_event_hook;
|
||||
|
||||
encryptBucketCredential(obj);
|
||||
let storedBucketCredentials = {};
|
||||
if (isObscureKey(obj?.bucket_credential)) {
|
||||
const [account] = await Account.retrieve(sid) || [];
|
||||
/* to avoid overwriting valid credentials with the obscured secret,
|
||||
* that the frontend might send, we pass the stored account bucket credentials
|
||||
* in the case it is a obscured key, we replace it with the stored one
|
||||
*/
|
||||
storedBucketCredentials = account.bucket_credential;
|
||||
}
|
||||
|
||||
encryptBucketCredential(obj, storedBucketCredentials);
|
||||
|
||||
const rowsAffected = await Account.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
@@ -836,6 +865,13 @@ router.post('/:sid/BucketCredentialTest', async(req, res) => {
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
/* if the req.body bucket credentials contain an obscured key, replace with stored account.bucket_credential */
|
||||
if (isObscureKey(req.body)) {
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const [account] = await Account.retrieve(account_sid, service_provider_sid) || [];
|
||||
if (!account) return res.status(404).end();
|
||||
req.body = account.bucket_credential;
|
||||
}
|
||||
const {vendor, name, region, access_key_id, secret_access_key, service_key, connection_string, endpoint} = req.body;
|
||||
const ret = {
|
||||
status: 'not tested'
|
||||
|
||||
@@ -156,7 +156,8 @@ router.get('/', async(req, res) => {
|
||||
try {
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
|
||||
const results = await Application.retrieveAll(service_provider_sid, account_sid);
|
||||
const name = req.query.name;
|
||||
const results = await Application.retrieveAll(service_provider_sid, account_sid, name);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
|
||||
@@ -4,6 +4,9 @@ const SpeechCredential = require('../../models/speech-credential');
|
||||
const decorate = require('./decorate');
|
||||
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const sysError = require('../error');
|
||||
const multer = require('multer');
|
||||
const upload = multer({ dest: '/tmp/csv/' });
|
||||
const fs = require('fs');
|
||||
|
||||
const validateCredentialPermission = async(req) => {
|
||||
const credential = await SpeechCredential.retrieve(req.body.speech_credential_sid);
|
||||
@@ -41,6 +44,33 @@ const preconditions = {
|
||||
|
||||
decorate(router, GoogleCustomVoice, ['add', 'retrieve', 'update', 'delete'], preconditions);
|
||||
|
||||
const voiceCloningKeySubString = (voice_cloning_key) => {
|
||||
return voice_cloning_key ? voice_cloning_key.substring(0, 100) + '...' : undefined;
|
||||
};
|
||||
|
||||
router.get('/: sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const {sid} = req.params;
|
||||
const account_sid = req.user.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid;
|
||||
|
||||
const google_voice = await GoogleCustomVoice.retrieve(sid);
|
||||
google_voice.voice_cloning_key = voiceCloningKeySubString(google_voice.voice_cloning_key);
|
||||
if (!google_voice) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
if (req.user.hasScope('service_provider') && google_voice.service_provider_sid !== service_provider_sid ||
|
||||
req.user.hasScope('account') && google_voice.account_sid !== account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
return res.status(200).json(google_voice);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const account_sid = req.user.account_sid || req.query.account_sid;
|
||||
@@ -67,7 +97,37 @@ router.get('/', async(req, res) => {
|
||||
}
|
||||
results = await GoogleCustomVoice.retrieveAllByLabel(service_provider_sid, account_sid, label);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
res.status(200).json(results.map((r) => {
|
||||
r.voice_cloning_key = voiceCloningKeySubString(r.voice_cloning_key);
|
||||
return r;
|
||||
}));
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:sid/VoiceCloningKey', upload.single('file'), async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
const {sid} = req.params;
|
||||
const account_sid = req.user.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid;
|
||||
try {
|
||||
const google_voice = await GoogleCustomVoice.retrieve(sid);
|
||||
if (!google_voice) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
if (req.user.hasScope('service_provider') && google_voice.service_provider_sid !== service_provider_sid ||
|
||||
req.user.hasScope('account') && google_voice.account_sid !== account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
const voice_cloning_key = Buffer.from(fs.readFileSync(req.file.path)).toString();
|
||||
await GoogleCustomVoice.update(sid, {
|
||||
voice_cloning_key
|
||||
});
|
||||
fs.unlinkSync(req.file.path);
|
||||
return res.sendStatus(204);
|
||||
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const debug = require('debug')('jambonz:api-server');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {promisePool} = require('../../db');
|
||||
const {doGithubAuth, doGoogleAuth, doLocalAuth} = require('../../utils/oauth-utils');
|
||||
const {validateEmail} = require('../../utils/email-utils');
|
||||
const {validateEmail, emailSimpleText} = require('../../utils/email-utils');
|
||||
const {cacheClient} = require('../../helpers');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const short = require('short-uuid');
|
||||
@@ -12,6 +12,7 @@ const jwt = require('jsonwebtoken');
|
||||
const {setupFreeTrial, createTestCdrs, createTestAlerts} = require('./utils');
|
||||
const {generateHashedPassword} = require('../../utils/password-utils');
|
||||
const sysError = require('../error');
|
||||
|
||||
const insertUserSql = `INSERT into users
|
||||
(user_sid, account_sid, name, email, provider, provider_userid, email_validated)
|
||||
values (?, ?, ?, ?, ?, ?, 1)`;
|
||||
@@ -36,6 +37,17 @@ const insertSignupHistorySql = `INSERT into signup_history
|
||||
(email, name)
|
||||
values (?, ?)`;
|
||||
|
||||
const slackEmail = `Hi there and welcome to jambonz!
|
||||
|
||||
We are excited to have you on board. Feel free to join the community on Slack at https://joinslack.jambonz.org,
|
||||
where you can connect with other jambonz users, ask questions, share your experiences, and learn from others.
|
||||
|
||||
Hope to see you there!
|
||||
|
||||
Best,
|
||||
|
||||
DaveH and the jambonz team`;
|
||||
|
||||
const addLocalUser = async(logger, user_sid, account_sid,
|
||||
name, email, email_activation_code, passwordHash, service_provider_sid) => {
|
||||
const [r] = await promisePool.execute(insertUserLocalSql,
|
||||
@@ -324,6 +336,15 @@ router.post('/', async(req, res) => {
|
||||
tutorial_completion: 0,
|
||||
scope: 'read-write'
|
||||
});
|
||||
|
||||
// send invite to Slack
|
||||
if (process.env.SEND_SLACK_INVITE_ON_SIGNUP) {
|
||||
try {
|
||||
emailSimpleText(logger, userProfile.email, 'Welcome to jambonz!', slackEmail);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'Error sending slack invite');
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (user_sid) {
|
||||
/* add a new user for existing account */
|
||||
|
||||
@@ -41,6 +41,7 @@ const checkUserScope = async(req, voip_carrier_sid) => {
|
||||
|
||||
const validate = async(req, sid) => {
|
||||
const {lookupSipGatewayBySid} = req.app.locals;
|
||||
const {netmask} = req.body;
|
||||
let voip_carrier_sid;
|
||||
|
||||
if (sid) {
|
||||
@@ -52,6 +53,12 @@ const validate = async(req, sid) => {
|
||||
voip_carrier_sid = req.body.voip_carrier_sid;
|
||||
if (!voip_carrier_sid) throw new DbErrorBadRequest('missing voip_carrier_sid');
|
||||
}
|
||||
if (netmask &&
|
||||
process.env.JAMBONZ_MIN_GATEWAY_NETMASK &&
|
||||
parseInt(netmask) < process.env.JAMBONZ_MIN_GATEWAY_NETMASK) {
|
||||
throw new DbErrorBadRequest(
|
||||
`netmask required to have value equal or greater than ${process.env.JAMBONZ_MIN_GATEWAY_NETMASK}`);
|
||||
}
|
||||
await checkUserScope(req, voip_carrier_sid);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ const {decryptCredential, testWhisper, testDeepgramTTS,
|
||||
testPlayHT,
|
||||
testRimelabs,
|
||||
testVerbioTts,
|
||||
testVerbioStt} = require('../../utils/speech-utils');
|
||||
testVerbioStt,
|
||||
testSpeechmaticsStt,
|
||||
testCartesia,
|
||||
testVoxistStt} = require('../../utils/speech-utils');
|
||||
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {
|
||||
testGoogleTts,
|
||||
@@ -122,8 +125,10 @@ const encryptCredential = (obj) => {
|
||||
secret,
|
||||
nuance_tts_uri,
|
||||
nuance_stt_uri,
|
||||
speechmatics_stt_uri,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
deepgram_tts_uri,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
@@ -138,6 +143,7 @@ const encryptCredential = (obj) => {
|
||||
instance_id,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
custom_tts_streaming_url,
|
||||
auth_token = '',
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
@@ -204,10 +210,10 @@ const encryptCredential = (obj) => {
|
||||
|
||||
case 'deepgram':
|
||||
// API key is optional if onprem
|
||||
if (!deepgram_stt_uri) {
|
||||
if (!deepgram_stt_uri || !deepgram_tts_uri) {
|
||||
assert(api_key, 'invalid deepgram speech credential: api_key is required');
|
||||
}
|
||||
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri, deepgram_stt_use_tls});
|
||||
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri, deepgram_stt_use_tls, deepgram_tts_uri});
|
||||
return encrypt(deepgramData);
|
||||
|
||||
case 'ibm':
|
||||
@@ -235,6 +241,12 @@ const encryptCredential = (obj) => {
|
||||
const elevenlabsData = JSON.stringify({api_key, model_id, options});
|
||||
return encrypt(elevenlabsData);
|
||||
|
||||
case 'speechmatics':
|
||||
assert(api_key, 'invalid speechmatics speech credential: api_key is required');
|
||||
assert(speechmatics_stt_uri, 'invalid speechmatics speech credential: speechmatics_stt_uri is required');
|
||||
const speechmaticsData = JSON.stringify({api_key, speechmatics_stt_uri, options});
|
||||
return encrypt(speechmaticsData);
|
||||
|
||||
case 'playht':
|
||||
assert(api_key, 'invalid playht speech credential: api_key is required');
|
||||
assert(user_id, 'invalid playht speech credential: user_id is required');
|
||||
@@ -242,6 +254,12 @@ const encryptCredential = (obj) => {
|
||||
const playhtData = JSON.stringify({api_key, user_id, voice_engine, options});
|
||||
return encrypt(playhtData);
|
||||
|
||||
case 'cartesia':
|
||||
assert(api_key, 'invalid cartesia speech credential: api_key is required');
|
||||
assert(model_id, 'invalid cartesia speech credential: model_id is required');
|
||||
const cartesiaData = JSON.stringify({api_key, model_id, options});
|
||||
return encrypt(cartesiaData);
|
||||
|
||||
case 'rimelabs':
|
||||
assert(api_key, 'invalid rimelabs speech credential: api_key is required');
|
||||
assert(model_id, 'invalid rimelabs speech credential: model_id is required');
|
||||
@@ -253,6 +271,11 @@ const encryptCredential = (obj) => {
|
||||
const assemblyaiData = JSON.stringify({api_key});
|
||||
return encrypt(assemblyaiData);
|
||||
|
||||
case 'voxist':
|
||||
assert(api_key, 'invalid voxist speech credential: api_key is required');
|
||||
const voxistData = JSON.stringify({api_key});
|
||||
return encrypt(voxistData);
|
||||
|
||||
case 'whisper':
|
||||
assert(api_key, 'invalid whisper speech credential: api_key is required');
|
||||
assert(model_id, 'invalid whisper speech credential: model_id is required');
|
||||
@@ -268,7 +291,7 @@ const encryptCredential = (obj) => {
|
||||
|
||||
default:
|
||||
if (vendor.startsWith('custom:')) {
|
||||
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url});
|
||||
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url, custom_tts_streaming_url});
|
||||
return encrypt(customData);
|
||||
}
|
||||
else assert(false, `invalid or missing vendor: ${vendor}`);
|
||||
@@ -452,12 +475,14 @@ router.put('/:sid', async(req, res) => {
|
||||
custom_stt_endpoint_url,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
custom_tts_streaming_url,
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
voice_engine,
|
||||
options,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
deepgram_tts_uri,
|
||||
engine_version
|
||||
} = req.body;
|
||||
|
||||
@@ -479,12 +504,14 @@ router.put('/:sid', async(req, res) => {
|
||||
nuance_tts_uri,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
custom_tts_streaming_url,
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
voice_engine,
|
||||
options,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
deepgram_tts_uri,
|
||||
engine_version
|
||||
};
|
||||
logger.info({o, newCred}, 'updating speech credential with this new credential');
|
||||
@@ -765,6 +792,18 @@ router.get('/:sid/test', async(req, res) => {
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'speechmatics') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testSpeechmaticsStt(logger, {api_key});
|
||||
results.stt.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.stt = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'playht') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
@@ -781,6 +820,22 @@ router.get('/:sid/test', async(req, res) => {
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'cartesia') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testCartesia(logger, synthAudio, credential);
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
let reason = err.message;
|
||||
// if error is from bent, let get the body
|
||||
try {
|
||||
reason = await err.text();
|
||||
} catch {}
|
||||
results.tts = {status: 'fail', reason};
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'rimelabs') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
@@ -804,6 +859,18 @@ router.get('/:sid/test', async(req, res) => {
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'voxist') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testVoxistStt(logger, {api_key});
|
||||
results.stt.status = 'ok';
|
||||
SpeechCredential.sttTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.stt = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'whisper') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
@@ -852,7 +919,7 @@ router.get('/:sid/test', async(req, res) => {
|
||||
router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
|
||||
const {logger, getTtsVoices} = req.app.locals;
|
||||
try {
|
||||
const {vendor, label} = req.query;
|
||||
const {vendor, label, create_new} = req.query;
|
||||
if (!vendor) {
|
||||
throw new DbErrorBadRequest('vendor is required');
|
||||
}
|
||||
@@ -860,7 +927,7 @@ router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
|
||||
const service_provider_sid = req.user.service_provider_sid ||
|
||||
req.body.service_provider_sid || parseServiceProviderSid(req);
|
||||
|
||||
const credentials = await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
|
||||
const credentials = create_new ? null : await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
|
||||
service_provider_sid, account_sid, vendor, label);
|
||||
const tmp = credentials && credentials.length > 0 ? credentials[0] : null;
|
||||
const cred = tmp ? JSON.parse(decrypt(tmp.credential)) : null;
|
||||
|
||||
@@ -440,6 +440,22 @@ const validatePasswordSettings = async(password) => {
|
||||
return;
|
||||
};
|
||||
|
||||
function hasValue(data) {
|
||||
if (typeof data === 'string') {
|
||||
return data && data.length > 0;
|
||||
} else if (Array.isArray(data)) {
|
||||
return data && data.length > 0;
|
||||
} else if (typeof data === 'object') {
|
||||
return data && Object.keys(data).length > 0;
|
||||
} else if (typeof data === 'number') {
|
||||
return data !== null;
|
||||
} else if (typeof data === 'boolean') {
|
||||
return data !== null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupFreeTrial,
|
||||
createTestCdrs,
|
||||
@@ -460,5 +476,6 @@ module.exports = {
|
||||
checkLimits,
|
||||
enableSubspace,
|
||||
disableSubspace,
|
||||
validatePasswordSettings
|
||||
validatePasswordSettings,
|
||||
hasValue,
|
||||
};
|
||||
|
||||
@@ -130,71 +130,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Accounts/{accountSid}/RecentCalls/{callSid}/record/{year}/{month}/{day}/{format}:
|
||||
get:
|
||||
tags:
|
||||
- Call Records
|
||||
summary: 'Retrieve a recording of a recent call in specified format'
|
||||
description: 'Gets the recording of a recent call by specifying the account ID, call ID, the date of the call, and the format of the recording (e.g., MP3 or WAV).'
|
||||
operationId: getRecordingByFormat
|
||||
security:
|
||||
- ApiKeyAuth: [ ]
|
||||
parameters:
|
||||
- name: accountSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: callSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: year
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: month
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: day
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: format
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- mp3
|
||||
- wav
|
||||
responses:
|
||||
200:
|
||||
description: Successfully retrieved the recording file.
|
||||
content:
|
||||
audio/*:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
404:
|
||||
description: Call record not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: System error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Sbcs:
|
||||
post:
|
||||
tags:
|
||||
|
||||
@@ -17,10 +17,15 @@ const encrypt = (text) => {
|
||||
};
|
||||
|
||||
const decrypt = (data) => {
|
||||
const hash = JSON.parse(data);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);
|
||||
return decrpyted.toString();
|
||||
try {
|
||||
const hash = JSON.parse(data);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);
|
||||
return decrpyted.toString();
|
||||
} catch (error) {
|
||||
console.error('Error while decrypting data', error);
|
||||
return '{}';
|
||||
}
|
||||
};
|
||||
|
||||
const obscureKey = (key, key_spoiler_length = 6) => {
|
||||
@@ -33,8 +38,85 @@ const obscureKey = (key, key_spoiler_length = 6) => {
|
||||
return `${key.slice(0, key_spoiler_length)}${key_spoiler_char.repeat(key.length - key_spoiler_length)}`;
|
||||
};
|
||||
|
||||
function isObscureKey(bucketCredentials) {
|
||||
if (!bucketCredentials) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
vendor,
|
||||
secret_access_key = '',
|
||||
service_key = '',
|
||||
connection_string = ''
|
||||
} = bucketCredentials || {};
|
||||
let pattern;
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
|
||||
return pattern.test(secret_access_key);
|
||||
case 'azure':
|
||||
pattern = /^https:[A-Za-z0-9\/.:?=&_-]+$/;
|
||||
return pattern.test(connection_string);
|
||||
|
||||
case 'google': {
|
||||
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
|
||||
let {private_key} = JSON.parse(service_key);
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
private_key = private_key.slice(key_header.length, private_key.length);
|
||||
return pattern.test(private_key || '');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log('Error in isObscureKey', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* obscure sensitive data in bucket credentials
|
||||
* an obscured key contains of 6 'spoiled' characters of the key followed by 'X' characters
|
||||
* '123456XXXXXXXXXXXXXXXXXXXXXXXX'
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
function obscureBucketCredentialsSensitiveData(obj) {
|
||||
if (!obj) return obj;
|
||||
const {vendor, service_key, connection_string, secret_access_key} = obj;
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
obj.secret_access_key = obscureKey(secret_access_key);
|
||||
break;
|
||||
case 'google':
|
||||
const o = JSON.parse(service_key);
|
||||
let private_key = o.private_key;
|
||||
if (!isObscureKey(obj)) {
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
private_key = o.private_key.slice(key_header.length, o.private_key.length);
|
||||
private_key = `${key_header}${obscureKey(private_key)}`;
|
||||
}
|
||||
const obscured = {
|
||||
...o,
|
||||
private_key
|
||||
};
|
||||
obj.service_key = JSON.stringify(obscured);
|
||||
break;
|
||||
case 'azure':
|
||||
obj.connection_string = obscureKey(connection_string);
|
||||
break;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
encrypt,
|
||||
decrypt,
|
||||
obscureKey
|
||||
obscureKey,
|
||||
isObscureKey,
|
||||
obscureBucketCredentialsSensitiveData,
|
||||
};
|
||||
|
||||
218
lib/utils/speech-data/stt-speechmatics.js
Normal file
218
lib/utils/speech-data/stt-speechmatics.js
Normal file
@@ -0,0 +1,218 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Automatic',
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
name: 'Arabic',
|
||||
value: 'ar',
|
||||
},
|
||||
{
|
||||
name: 'Bashkir',
|
||||
value: 'ba',
|
||||
},
|
||||
{
|
||||
name: 'Basque',
|
||||
value: 'eu',
|
||||
},
|
||||
{
|
||||
name: 'Belarusian',
|
||||
value: 'be',
|
||||
},
|
||||
{
|
||||
name: 'Bulgarian',
|
||||
value: 'bg',
|
||||
},
|
||||
{
|
||||
name: 'Cantonese',
|
||||
value: 'yue',
|
||||
},
|
||||
{
|
||||
name: 'Catalan',
|
||||
value: 'ca',
|
||||
},
|
||||
{
|
||||
name: 'Croatian',
|
||||
value: 'hr',
|
||||
},
|
||||
{
|
||||
name: 'Czech',
|
||||
value: 'cs',
|
||||
},
|
||||
{
|
||||
name: 'Danish',
|
||||
value: 'da',
|
||||
},
|
||||
{
|
||||
name: 'Dutch',
|
||||
value: 'nl',
|
||||
},
|
||||
{
|
||||
name: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
name: 'Esperanto',
|
||||
value: 'eo',
|
||||
},
|
||||
{
|
||||
name: 'Estonian',
|
||||
value: 'et',
|
||||
},
|
||||
{
|
||||
name: 'Finnish',
|
||||
value: 'fi',
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
value: 'fr',
|
||||
},
|
||||
{
|
||||
name: 'Galician',
|
||||
value: 'gl',
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
value: 'de',
|
||||
},
|
||||
{
|
||||
name: 'Greek',
|
||||
value: 'el',
|
||||
},
|
||||
{
|
||||
name: 'Hebrew',
|
||||
value: 'he',
|
||||
},
|
||||
{
|
||||
name: 'Hindi',
|
||||
value: 'hi',
|
||||
},
|
||||
{
|
||||
name: 'Hungarian',
|
||||
value: 'hu',
|
||||
},
|
||||
{
|
||||
name: 'Irish',
|
||||
value: 'ga',
|
||||
},
|
||||
{
|
||||
name: 'Interlingua',
|
||||
value: 'ia',
|
||||
},
|
||||
{
|
||||
name: 'Italian',
|
||||
value: 'it',
|
||||
},
|
||||
{
|
||||
name: 'Indonesian',
|
||||
value: 'id',
|
||||
},
|
||||
{
|
||||
name: 'Japanese',
|
||||
value: 'ja',
|
||||
},
|
||||
{
|
||||
name: 'Korean',
|
||||
value: 'ko',
|
||||
},
|
||||
{
|
||||
name: 'Latvian',
|
||||
value: 'lv',
|
||||
},
|
||||
{
|
||||
name: 'Lithuanian',
|
||||
value: 'lt',
|
||||
},
|
||||
{
|
||||
name: 'Maltese',
|
||||
value: 'mt',
|
||||
},
|
||||
{
|
||||
name: 'Malay',
|
||||
value: 'ms',
|
||||
},
|
||||
{
|
||||
name: 'Mandarin',
|
||||
value: 'cmn',
|
||||
},
|
||||
{
|
||||
name: 'Marathi',
|
||||
value: 'mr',
|
||||
},
|
||||
{
|
||||
name: 'Mongolian',
|
||||
value: 'mn',
|
||||
},
|
||||
{
|
||||
name: 'Norwegian',
|
||||
value: 'no',
|
||||
},
|
||||
{
|
||||
name: 'Persian',
|
||||
value: 'fa',
|
||||
},
|
||||
{
|
||||
name: 'Polish',
|
||||
value: 'pl',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese',
|
||||
value: 'pt',
|
||||
},
|
||||
{
|
||||
name: 'Romanian',
|
||||
value: 'ro',
|
||||
},
|
||||
{
|
||||
name: 'Russian',
|
||||
value: 'ru',
|
||||
},
|
||||
{
|
||||
name: 'Slovakian',
|
||||
value: 'sk',
|
||||
},
|
||||
{
|
||||
name: 'Slovenian',
|
||||
value: 'sl',
|
||||
},
|
||||
{
|
||||
name: 'Spanish',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
name: 'Spanish & English bilingual',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
name: 'Swedish',
|
||||
value: 'sv',
|
||||
},
|
||||
{
|
||||
name: 'Tamil',
|
||||
value: 'ta',
|
||||
},
|
||||
{
|
||||
name: 'Thai',
|
||||
value: 'th',
|
||||
},
|
||||
{
|
||||
name: 'Turkish',
|
||||
value: 'tr',
|
||||
},
|
||||
{
|
||||
name: 'Uyghur',
|
||||
value: 'ug',
|
||||
},
|
||||
{
|
||||
name: 'Ukrainian',
|
||||
value: 'uk',
|
||||
},
|
||||
{
|
||||
name: 'Vietnamese',
|
||||
value: 'vi',
|
||||
},
|
||||
{
|
||||
name: 'Welsh',
|
||||
value: 'cy',
|
||||
},
|
||||
];
|
||||
8
lib/utils/speech-data/stt-voxist.js
Normal file
8
lib/utils/speech-data/stt-voxist.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = [
|
||||
{ name: 'English', value: 'en' },
|
||||
{ name: 'French', value: 'fr' },
|
||||
{ name: 'German', value: 'de' },
|
||||
{ name: 'Dutch', value: 'nl' },
|
||||
{ name: 'Italian', value: 'it' },
|
||||
{ name: 'Spanish', value: 'sp' },
|
||||
];
|
||||
301
lib/utils/speech-data/tts-cartesia.js
Normal file
301
lib/utils/speech-data/tts-cartesia.js
Normal file
@@ -0,0 +1,301 @@
|
||||
/* eslint-disable max-len */
|
||||
module.exports = [
|
||||
{
|
||||
value: 'en',
|
||||
name: 'English',
|
||||
voices: [
|
||||
{
|
||||
value: '79f8b5fb-2cc8-479a-80df-29f7a7cf1a3e',
|
||||
name: 'Nonfiction Man - This voice is smooth, confident, and resonant, perfect for narrating educational content',
|
||||
},
|
||||
{
|
||||
value: 'e00d0e4c-a5c8-443f-a8a3-473eb9a62355',
|
||||
name: 'Friendly Sidekick - This voice is friendly and supportive, designed for voicing characters in games and videos',
|
||||
},
|
||||
{
|
||||
value: '3b554273-4299-48b9-9aaf-eefd438e3941',
|
||||
name: 'Indian Lady - This voice is young, rich, and curious, perfect for a narrator or fictional character',
|
||||
},
|
||||
{
|
||||
value: '71a7ad14-091c-4e8e-a314-022ece01c121',
|
||||
name: 'British Reading Lady - This is a calm and elegant voice with a British accent, perfect for storytelling and narration',
|
||||
},
|
||||
{
|
||||
value: '4d2fd738-3b3d-4368-957a-bb4805275bd9',
|
||||
name: 'British Narration Lady - This is a neutral voice with a British accent, perfect for narrations ',
|
||||
},
|
||||
{
|
||||
value: '15a9cd88-84b0-4a8b-95f2-5d583b54c72e',
|
||||
name: 'Reading Lady - This voice is monotone and deliberate, perfect for a slower-paced and more serious reading voice',
|
||||
},
|
||||
{
|
||||
value: 'd46abd1d-2d02-43e8-819f-51fb652c1c61',
|
||||
name: 'Newsman - This voice is neutral and educational, perfect for a news anchor',
|
||||
},
|
||||
{
|
||||
value: '2ee87190-8f84-4925-97da-e52547f9462c',
|
||||
name: 'Child - This voice is young and full, perfect for a child',
|
||||
},
|
||||
{
|
||||
value: 'cd17ff2d-5ea4-4695-be8f-42193949b946',
|
||||
name: 'Meditation Lady - This voice is calm, soothing, and relaxing, perfect for meditation',
|
||||
},
|
||||
{
|
||||
value: '5345cf08-6f37-424d-a5d9-8ae1101b9377',
|
||||
name: 'Maria - This voice is laid back, natural, and conversational, like you\'re catching up with a good friend',
|
||||
},
|
||||
{
|
||||
value: '41534e16-2966-4c6b-9670-111411def906',
|
||||
name: '1920\'s Radioman - This voice is energetic and confident, great for an entertainer or radio host',
|
||||
},
|
||||
{
|
||||
value: 'bf991597-6c13-47e4-8411-91ec2de5c466',
|
||||
name: 'Newslady - This voice is authoritative and educational, perfect for a news anchor',
|
||||
},
|
||||
{
|
||||
value: '00a77add-48d5-4ef6-8157-71e5437b282d',
|
||||
name: 'Calm Lady - This voice is calm and nurturing, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '156fb8d2-335b-4950-9cb3-a2d33befec77',
|
||||
name: 'Helpful Woman - This voice is friendly and conversational, designed for customer support agents and casual conversations',
|
||||
},
|
||||
{
|
||||
value: '36b42fcb-60c5-4bec-b077-cb1a00a92ec6',
|
||||
name: 'Pilot over Intercom - This voice sounds like a British Pilot character speaking over an Intercom',
|
||||
},
|
||||
{
|
||||
value: 'f146dcec-e481-45be-8ad2-96e1e40e7f32',
|
||||
name: 'Reading Man - Male with calm narrational voice.',
|
||||
},
|
||||
{
|
||||
value: '34575e71-908f-4ab6-ab54-b08c95d6597d',
|
||||
name: 'New York Man - This voice is compelling and husky, with a New York accent, perfect for sales pitches and motivational content',
|
||||
},
|
||||
{
|
||||
value: 'a0e99841-438c-4a64-b679-ae501e7d6091',
|
||||
name: 'Barbershop Man - This voice is smooth and relaxing, perfect for a casual conversation',
|
||||
},
|
||||
{
|
||||
value: '638efaaa-4d0c-442e-b701-3fae16aad012',
|
||||
name: 'Indian Man - This voice is smooth with an Indian accent, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '41f3c367-e0a8-4a85-89e0-c27bae9c9b6d',
|
||||
name: 'Australian Customer Support Man - This voice is warm with an Australian accent, perfect for customer support agents',
|
||||
},
|
||||
{
|
||||
value: '421b3369-f63f-4b03-8980-37a44df1d4e8',
|
||||
name: 'Friendly Australian Man - This voice is rich and deep, with an Australian accent, perfect for casual conversations with a friend',
|
||||
},
|
||||
{
|
||||
value: 'b043dea0-a007-4bbe-a708-769dc0d0c569',
|
||||
name: 'Wise Man - This is a deep and deliberate voice, suited for educational content and conversations',
|
||||
},
|
||||
{
|
||||
value: '69267136-1bdc-412f-ad78-0caad210fb40',
|
||||
name: 'Friendly Reading Man - This voice is energetic and friendly, like having your friend read his favorite book to you',
|
||||
},
|
||||
{
|
||||
value: 'a167e0f3-df7e-4d52-a9c3-f949145efdab',
|
||||
name: 'Customer Support Man - This voice is clear and calm, perfect for a call center',
|
||||
},
|
||||
{
|
||||
value: '4f8651b0-bbbd-46ac-8b37-5168c5923303',
|
||||
name: 'Kentucky Woman - This voice is energetic and upbeat, with a slight Kentucky accent, perfect for speeches and rallies',
|
||||
},
|
||||
{
|
||||
value: 'daf747c6-6bc2-4083-bd59-aa94dce23f5d',
|
||||
name: 'Middle Eastern Woman - This voice is clear with a Middle Eastern Accent, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '694f9389-aac1-45b6-b726-9d9369183238',
|
||||
name: 'Sarah - This voice is natural and expressive with an American accent, perfect for a wide range of conversational use cases including customer support, sales, reception, and more.',
|
||||
},
|
||||
{
|
||||
value: '794f9389-aac1-45b6-b726-9d9369183238',
|
||||
name: 'Sarah Curious - This voice is similar to Sarah, but has improved emphasis for questions.',
|
||||
},
|
||||
{
|
||||
value: '21b81c14-f85b-436d-aff5-43f2e788ecf8',
|
||||
name: 'Laidback Woman - This voice is laid back and husky, with a slight Californian accent',
|
||||
},
|
||||
{
|
||||
value: 'a3520a8f-226a-428d-9fcd-b0a4711a6829',
|
||||
name: 'Reflective Woman - This voice is even, full, and reflective, perfect for a young narrator for an audiobook or movie',
|
||||
},
|
||||
{
|
||||
value: '829ccd10-f8b3-43cd-b8a0-4aeaa81f3b30',
|
||||
name: 'Customer Support Lady - This voice is polite and helpful, perfect for customer support agents',
|
||||
},
|
||||
{
|
||||
value: '79a125e8-cd45-4c13-8a67-188112f4dd22',
|
||||
name: 'British Lady - This voice is elegant with a slight British accent, perfect for storytelling and narrating',
|
||||
},
|
||||
{
|
||||
value: 'c8605446-247c-4d39-acd4-8f4c28aa363c',
|
||||
name: 'Wise Lady - This voice is wise and authoritative, perfect for a confident narrator',
|
||||
},
|
||||
{
|
||||
value: '8985388c-1332-4ce7-8d55-789628aa3df4',
|
||||
name: 'Australian Narrator Lady - This voice is even and neutral, with an Australian accent, designed for narrating content and stories',
|
||||
},
|
||||
{
|
||||
value: 'ff1bb1a9-c582-4570-9670-5f46169d0fc8',
|
||||
name: 'Indian Customer Support Lady - This voice is clear and polite, with an Indian accent, suitable for customer support agents',
|
||||
},
|
||||
{
|
||||
value: '820a3788-2b37-4d21-847a-b65d8a68c99a',
|
||||
name: 'Salesman - This voice is smooth and persuasive, perfect for sales pitches and phone conversations',
|
||||
},
|
||||
{
|
||||
value: 'f114a467-c40a-4db8-964d-aaba89cd08fa',
|
||||
name: 'Yogaman - This voice is calm, soothing, and stable, perfect for a yoga instructor',
|
||||
},
|
||||
{
|
||||
value: 'c45bc5ec-dc68-4feb-8829-6e6b2748095d',
|
||||
name: 'Movieman - This voice is deep, resonant, and assertive, perfect for a movie narrator',
|
||||
},
|
||||
{
|
||||
value: '87748186-23bb-4158-a1eb-332911b0b708',
|
||||
name: 'Wizardman - This voice is wise and mysterious, perfect for a Wizard character',
|
||||
},
|
||||
{
|
||||
value: '043cfc81-d69f-4bee-ae1e-7862cb358650',
|
||||
name: 'Australian Woman - This voice is deliberate and confident, with a slight Australian accent, perfect for inspiring characters in videos and stories',
|
||||
},
|
||||
{
|
||||
value: '5619d38c-cf51-4d8e-9575-48f61a280413',
|
||||
name: 'Announcer Man - This voice is deep and inviting, perfect for entertainment and broadcasting content',
|
||||
},
|
||||
{
|
||||
value: '42b39f37-515f-4eee-8546-73e841679c1d',
|
||||
name: 'Wise Guide Man - This voice is deep and deliberate, perfect for inspiring and guiding characters in games and videos',
|
||||
},
|
||||
{
|
||||
value: '565510e8-6b45-45de-8758-13588fbaec73',
|
||||
name: 'Midwestern Man - This voice is neutral and smooth, with a slight midwestern accent, perfect for narrations',
|
||||
},
|
||||
{
|
||||
value: '726d5ae5-055f-4c3d-8355-d9677de68937',
|
||||
name: 'Kentucky Man - This voice is laidback and smooth, with a Kentucky accent, perfect for a casual conversation',
|
||||
},
|
||||
{
|
||||
value: '63ff761f-c1e8-414b-b969-d1833d1c870c',
|
||||
name: 'Confident British Man - This voice is disciplined with a British accent, perfect for a commanding character or narrator',
|
||||
},
|
||||
{
|
||||
value: '98a34ef2-2140-4c28-9c71-663dc4dd7022',
|
||||
name: 'Southern Man - This voice is warm with a Southern accent, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '95856005-0332-41b0-935f-352e296aa0df',
|
||||
name: 'Classy British Man - This voice is light and smooth with a British accent, perfect for casual conversation',
|
||||
},
|
||||
{
|
||||
value: 'ee7ea9f8-c0c1-498c-9279-764d6b56d189',
|
||||
name: 'Polite Man - This voice is polite and conversational, with a slight accent, designed for customer support and casual conversations',
|
||||
},
|
||||
{
|
||||
value: '40104aff-a015-4da1-9912-af950fbec99e',
|
||||
name: 'Alabama Male - This voice has a strong Southern Accent, perfect for conversations and instructional videos',
|
||||
},
|
||||
{
|
||||
value: '13524ffb-a918-499a-ae97-c98c7c4408c4',
|
||||
name: 'Australian Male - This voice is smooth and disciplined, with an Australian Accent, suited for narrating educational content',
|
||||
},
|
||||
{
|
||||
value: '1001d611-b1a8-46bd-a5ca-551b23505334',
|
||||
name: 'Anime Girl - This voice is expressive and has a high pitch, suitable for anime or gaming characters',
|
||||
},
|
||||
{
|
||||
value: 'e3827ec5-697a-4b7c-9704-1a23041bbc51',
|
||||
name: 'Sweet Lady - This voice is sweet and passionate, perfect for a character in a game or book',
|
||||
},
|
||||
{
|
||||
value: 'c2ac25f9-ecc4-4f56-9095-651354df60c0',
|
||||
name: 'Commercial Lady - This voice is inviting, enthusiastic, and relatable, perfect for a commercial or advertisement',
|
||||
},
|
||||
{
|
||||
value: '573e3144-a684-4e72-ac2b-9b2063a50b53',
|
||||
name: 'Teacher Lady - This voice is neutral and clear, perfect for narrating educational content',
|
||||
},
|
||||
{
|
||||
value: '8f091740-3df1-4795-8bd9-dc62d88e5131',
|
||||
name: 'Princess - This voice is light, freindly and has a flourish, perfect for character work in videos and games',
|
||||
},
|
||||
{
|
||||
value: '7360f116-6306-4e9a-b487-1235f35a0f21',
|
||||
name: 'Commercial Man - This voice is upbeat and enthusiastic, perfect for commercials and advertisements',
|
||||
},
|
||||
{
|
||||
value: '03496517-369a-4db1-8236-3d3ae459ddf7',
|
||||
name: 'ASMR Lady - This voice is calming and soft, perfect for guided meditations and soothing content',
|
||||
},
|
||||
{
|
||||
value: '248be419-c632-4f23-adf1-5324ed7dbf1d',
|
||||
name: 'Professional Woman - This voice is neutral and calm, perfect for a call center',
|
||||
},
|
||||
{
|
||||
value: 'bd9120b6-7761-47a6-a446-77ca49132781',
|
||||
name: 'Tutorial Man - This voice is inviting and calming, perfect for tutorials',
|
||||
},
|
||||
{
|
||||
value: '34bde396-9fde-4ebf-ad03-e3a1d1155205',
|
||||
name: 'New York Woman - This voice commands authority, with a New York accent, perfect for a commanding narrator or character',
|
||||
},
|
||||
{
|
||||
value: '11af83e2-23eb-452f-956e-7fee218ccb5c',
|
||||
name: 'Midwestern Woman - This voice is neutral and deliberate, with a midwestern accent, suitable for news broadcasts and narration',
|
||||
},
|
||||
{
|
||||
value: 'ed81fd13-2016-4a49-8fe3-c0d2761695fc',
|
||||
name: 'Sportsman - This voice is energetic and enthusiastic, perfect for a sports broadcaster',
|
||||
},
|
||||
{
|
||||
value: '996a8b96-4804-46f0-8e05-3fd4ef1a87cd',
|
||||
name: 'Storyteller Lady - This voice is neutral and smooth, with a slight Canadian accent, perfect for narrations',
|
||||
},
|
||||
{
|
||||
value: 'fb26447f-308b-471e-8b00-8e9f04284eb5',
|
||||
name: 'Doctor Mischief - This is an expressive character voice, suited to whimsical characters for games and educational content',
|
||||
},
|
||||
{
|
||||
value: '50d6beb4-80ea-4802-8387-6c948fe84208',
|
||||
name: 'The Merchant - This voice is playful and quirky, designed for character work in games and videos',
|
||||
},
|
||||
{
|
||||
value: 'e13cae5c-ec59-4f71-b0a6-266df3c9bb8e',
|
||||
name: 'Madame Mischief - This voice is mischeivious and playful, suitable for voicing characters for kids content and games',
|
||||
},
|
||||
{
|
||||
value: '5c42302c-194b-4d0c-ba1a-8cb485c84ab9',
|
||||
name: 'Female Nurse - This voice is clear and firm, perfect for nurse characters and instructional videos',
|
||||
},
|
||||
{
|
||||
value: 'f9836c6e-a0bd-460e-9d3c-f7299fa60f94',
|
||||
name: 'Southern Woman - This voice is friendly and inviting, with a slight Southern Accent, perfect for conversations and phone calls',
|
||||
},
|
||||
{
|
||||
value: 'a01c369f-6d2d-4185-bc20-b32c225eab70',
|
||||
name: 'British Customer Support Lady - This voice is friendly and polite, with a British accent, perfect for phone conversations',
|
||||
},
|
||||
{
|
||||
value: 'b7d50908-b17c-442d-ad8d-810c63997ed9',
|
||||
name: 'California Girl - This voice is enthusiastic and friendly, perfect for a casual conversation between friends',
|
||||
},
|
||||
{
|
||||
value: 'f785af04-229c-4a7c-b71b-f3194c7f08bb',
|
||||
name: 'John - This voice is natural and empathetic with an American accent, perfect for use cases like demos and customer support calls.',
|
||||
},
|
||||
{
|
||||
value: '729651dc-c6c3-4ee5-97fa-350da1f88600',
|
||||
name: 'Pleasant Man - A pleasant male voice that\'s good for use cases like demos and customer support calls',
|
||||
},
|
||||
{
|
||||
value: '91b4cf29-5166-44eb-8054-30d40ecc8081',
|
||||
name: 'Anna - This voice is natural and expressive with an American accent, perfect for use cases like interviews and customer support calls.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
28
lib/utils/speech-data/tts-deepgram.js
Normal file
28
lib/utils/speech-data/tts-deepgram.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const TtsDeepgramLanguagesVoiceRaw = require('./tts-model-deepgram');
|
||||
|
||||
const languagesVoices = [];
|
||||
|
||||
TtsDeepgramLanguagesVoiceRaw.forEach((data) => {
|
||||
const lang = languagesVoices.find((l) => {
|
||||
return l.value === data.locale;
|
||||
});
|
||||
|
||||
if (!lang) {
|
||||
languagesVoices.push({
|
||||
value: data.locale,
|
||||
name: data.localeName,
|
||||
voices: TtsDeepgramLanguagesVoiceRaw
|
||||
.filter((d) => {
|
||||
return d.locale === data.locale;
|
||||
})
|
||||
.map((d) => {
|
||||
return {
|
||||
value: d.value,
|
||||
name: `${d.name}`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = languagesVoices;
|
||||
152
lib/utils/speech-data/tts-languages-playht.js
Normal file
152
lib/utils/speech-data/tts-languages-playht.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// languages.js
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
name: 'English',
|
||||
value: 'english'
|
||||
},
|
||||
{
|
||||
name: 'Mandarin',
|
||||
value: 'mandarin'
|
||||
},
|
||||
{
|
||||
name: 'Hindi',
|
||||
value: 'hindi'
|
||||
},
|
||||
{
|
||||
name: 'Japanese',
|
||||
value: 'japanese'
|
||||
},
|
||||
{
|
||||
name: 'Korean',
|
||||
value: 'korean'
|
||||
},
|
||||
{
|
||||
name: 'Arabic',
|
||||
value: 'arabic'
|
||||
},
|
||||
{
|
||||
name: 'Spanish',
|
||||
value: 'spanish'
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
value: 'french'
|
||||
},
|
||||
{
|
||||
name: 'Italian',
|
||||
value: 'italian'
|
||||
},
|
||||
{
|
||||
name: 'Portuguese',
|
||||
value: 'portuguese'
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
value: 'german'
|
||||
},
|
||||
{
|
||||
name: 'Dutch',
|
||||
value: 'dutch'
|
||||
},
|
||||
{
|
||||
name: 'Swedish',
|
||||
value: 'swedish'
|
||||
},
|
||||
{
|
||||
name: 'Czech',
|
||||
value: 'czech'
|
||||
},
|
||||
{
|
||||
name: 'Polish',
|
||||
value: 'polish'
|
||||
},
|
||||
{
|
||||
name: 'Russian',
|
||||
value: 'russian'
|
||||
},
|
||||
{
|
||||
name: 'Bulgarian',
|
||||
value: 'bulgarian'
|
||||
},
|
||||
{
|
||||
name: 'Hebrew',
|
||||
value: 'hebrew'
|
||||
},
|
||||
{
|
||||
name: 'Greek',
|
||||
value: 'greek'
|
||||
},
|
||||
{
|
||||
name: 'Turkish',
|
||||
value: 'turkish'
|
||||
},
|
||||
{
|
||||
name: 'Afrikaans',
|
||||
value: 'afrikaans'
|
||||
},
|
||||
{
|
||||
name: 'Xhosa',
|
||||
value: 'xhosa'
|
||||
},
|
||||
{
|
||||
name: 'Tagalog',
|
||||
value: 'tagalog'
|
||||
},
|
||||
{
|
||||
name: 'Malay',
|
||||
value: 'malay'
|
||||
},
|
||||
{
|
||||
name: 'Indonesian',
|
||||
value: 'indonesian'
|
||||
},
|
||||
{
|
||||
name: 'Bengali',
|
||||
value: 'bengali'
|
||||
},
|
||||
{
|
||||
name: 'Serbian',
|
||||
value: 'serbian'
|
||||
},
|
||||
{
|
||||
name: 'Thai',
|
||||
value: 'thai'
|
||||
},
|
||||
{
|
||||
name: 'Urdu',
|
||||
value: 'urdu'
|
||||
},
|
||||
{
|
||||
name: 'Croatian',
|
||||
value: 'croatian'
|
||||
},
|
||||
{
|
||||
name: 'Hungarian',
|
||||
value: 'hungarian'
|
||||
},
|
||||
{
|
||||
name: 'Danish',
|
||||
value: 'danish'
|
||||
},
|
||||
{
|
||||
name: 'Amharic',
|
||||
value: 'amharic'
|
||||
},
|
||||
{
|
||||
name: 'Albanian',
|
||||
value: 'albanian'
|
||||
},
|
||||
{
|
||||
name: 'Catalan',
|
||||
value: 'catalan'
|
||||
},
|
||||
{
|
||||
name: 'Ukrainian',
|
||||
value: 'ukrainian'
|
||||
},
|
||||
{
|
||||
name: 'Galician',
|
||||
value: 'galician'
|
||||
}
|
||||
];
|
||||
25
lib/utils/speech-data/tts-model-cartesia.js
Normal file
25
lib/utils/speech-data/tts-model-cartesia.js
Normal file
@@ -0,0 +1,25 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Sonic',
|
||||
value: 'sonic',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
{ name: 'Sonic Preview', value: 'sonic-preview', languages: ['en'] },
|
||||
{
|
||||
name: 'Sonic 2024-12-12',
|
||||
value: 'sonic-2024-12-12',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
{
|
||||
name: 'Sonic 2024-10-19',
|
||||
value: 'sonic-2024-10-19',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
{ name: 'Sonic English', value: 'sonic-english', languages: ['en'] },
|
||||
{
|
||||
name: 'Sonic Multilingual',
|
||||
value: 'sonic-multilingual',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,14 +1,68 @@
|
||||
module.exports = [
|
||||
{ name: 'Asteria English (US) Female', value: 'aura-asteria-en' },
|
||||
{ name: 'Luna English (US) Female', value: 'aura-luna-en' },
|
||||
{ name: 'Stella English (US) Female', value: 'aura-stella-en' },
|
||||
{ name: 'Stella English (UK) Female', value: 'aura-athena-en' },
|
||||
{ name: 'Hera English (US) Female', value: 'aura-hera-en' },
|
||||
{ name: 'Orion English (US) Male', value: 'aura-orion-en' },
|
||||
{ name: 'Arcas English (US) Male', value: 'aura-arcas-en' },
|
||||
{ name: 'Perseus English (US) Male', value: 'aura-perseus-en' },
|
||||
{ name: 'Angus English (Ireland) Male', value: 'aura-angus-en' },
|
||||
{ name: 'Orpheus English (US) Male', value: 'aura-orpheus-en' },
|
||||
{ name: 'Helios English (UK) Male', value: 'aura-helios-en' },
|
||||
{ name: 'Zeus English (US) Male', value: 'aura-zeus-en' },
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Asteria English (US) Female',
|
||||
value: 'aura-asteria-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Luna English (US) Female',
|
||||
value: 'aura-luna-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Stella English (US) Female',
|
||||
value: 'aura-stella-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-GB',
|
||||
localeName: 'English (UK)',
|
||||
name: 'Stella English (UK) Female',
|
||||
value: 'aura-athena-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Hera English (US) Female',
|
||||
value: 'aura-hera-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Orion English (US) Male',
|
||||
value: 'aura-orion-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Arcas English (US) Male',
|
||||
value: 'aura-arcas-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Perseus English (US) Male',
|
||||
value: 'aura-perseus-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-IE',
|
||||
localeName: 'English (Ireland)',
|
||||
name: 'Angus English (Ireland) Male',
|
||||
value: 'aura-angus-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Orpheus English (US) Male',
|
||||
value: 'aura-orpheus-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Zeus English (US) Male',
|
||||
value: 'aura-zeus-en'
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
module.exports = [
|
||||
{ name: 'Turbo v2', value: 'eleven_turbo_v2' },
|
||||
{ name: 'Multilingual v2', value: 'eleven_multilingual_v2' },
|
||||
{ name: 'Turbo v2.5', value: 'eleven_turbo_v2_5' },
|
||||
{ name: 'Flash v2', value: 'eleven_flash_v2' },
|
||||
{ name: 'Flash v2.5', value: 'eleven_flash_v2_5' },
|
||||
{ name: 'Multilingual v1', value: 'eleven_multilingual_v1' },
|
||||
{ name: 'Multilingual v2', value: 'eleven_multilingual_v2' },
|
||||
{ name: 'Multilingual STS v2', value: 'eleven_multilingual_sts_v2' },
|
||||
{ name: 'English v1', value: 'eleven_monolingual_v1' },
|
||||
{ name: 'English v2', value: 'eleven_english_sts_v2' },
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = [
|
||||
{ name: 'Play3.0', value: 'Play3.0' },
|
||||
{ name: 'PlayHT2.0-turbo', value: 'PlayHT2.0-turbo' },
|
||||
{ name: 'PlayHT2.0', value: 'PlayHT2.0' },
|
||||
{ name: 'PlayHT1.0', value: 'PlayHT1.0' },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = [
|
||||
{ name: 'Mist', value: 'mist' },
|
||||
{ name: 'Mistv2', value: 'mistv2' },
|
||||
{ name: 'V1', value: 'v1' },
|
||||
];
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'US English',
|
||||
voices: [
|
||||
{
|
||||
value: 'tommy_en_us',
|
||||
name: 'Tommy-Male',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Castilian Spanish',
|
||||
@@ -27,12 +17,6 @@ module.exports = [
|
||||
value: 'miguel_es_pe',
|
||||
name: 'Miguel-Male',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-PE',
|
||||
name: 'Peruvian Spanish',
|
||||
voices: [
|
||||
{
|
||||
value: 'luz_es_pe',
|
||||
name: 'Luz-Female',
|
||||
|
||||
@@ -7,6 +7,7 @@ const bent = require('bent');
|
||||
const fs = require('fs');
|
||||
const { AssemblyAI } = require('assemblyai');
|
||||
const {decrypt, obscureKey} = require('./encrypt-decrypt');
|
||||
const { RealtimeSession } = require('speechmatics');
|
||||
|
||||
const TtsGoogleLanguagesVoices = require('./speech-data/tts-google');
|
||||
const TtsAwsLanguagesVoices = require('./speech-data/tts-aws');
|
||||
@@ -21,10 +22,13 @@ const TtsPlayHtLanguagesVoices = require('./speech-data/tts-playht');
|
||||
const TtsVerbioLanguagesVoices = require('./speech-data/tts-verbio');
|
||||
|
||||
const TtsModelDeepgram = require('./speech-data/tts-model-deepgram');
|
||||
const TtsLanguagesDeepgram = require('./speech-data/tts-deepgram');
|
||||
const TtsModelElevenLabs = require('./speech-data/tts-model-elevenlabs');
|
||||
const TtsModelWhisper = require('./speech-data/tts-model-whisper');
|
||||
const TtsModelPlayHT = require('./speech-data/tts-model-playht');
|
||||
const ttsLanguagesPlayHt = require('./speech-data/tts-languages-playht');
|
||||
const TtsModelRimelabs = require('./speech-data/tts-model-rimelabs');
|
||||
const TtsModelCartesia = require('./speech-data/tts-model-cartesia');
|
||||
|
||||
const SttGoogleLanguagesVoices = require('./speech-data/stt-google');
|
||||
const SttAwsLanguagesVoices = require('./speech-data/stt-aws');
|
||||
@@ -35,8 +39,13 @@ const SttIbmLanguagesVoices = require('./speech-data/stt-ibm');
|
||||
const SttNvidiaLanguagesVoices = require('./speech-data/stt-nvidia');
|
||||
const SttCobaltLanguagesVoices = require('./speech-data/stt-cobalt');
|
||||
const SttSonioxLanguagesVoices = require('./speech-data/stt-soniox');
|
||||
const SttSpeechmaticsLanguagesVoices = require('./speech-data/stt-speechmatics');
|
||||
const SttAssemblyaiLanguagesVoices = require('./speech-data/stt-assemblyai');
|
||||
const SttVoxistLanguagesVoices = require('./speech-data/stt-voxist');
|
||||
const SttVerbioLanguagesVoices = require('./speech-data/stt-verbio');
|
||||
const ttsCartesia = require('./speech-data/tts-cartesia');
|
||||
const ttsModelCartesia = require('./speech-data/tts-model-cartesia');
|
||||
|
||||
|
||||
const testSonioxStt = async(logger, credentials) => {
|
||||
const api_key = credentials;
|
||||
@@ -54,6 +63,61 @@ const testSonioxStt = async(logger, credentials) => {
|
||||
});
|
||||
};
|
||||
|
||||
const testSpeechmaticsStt = async(logger, credentials) => {
|
||||
const {api_key, speechmatics_stt_uri} = credentials;
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const session = new RealtimeSession({ apiKey: api_key, realtimeUrl: speechmatics_stt_uri });
|
||||
let transcription = '';
|
||||
session.addListener('Error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
session.addListener('AddTranscript', (message) => {
|
||||
transcription += message.metadata.transcript;
|
||||
});
|
||||
|
||||
session.addListener('EndOfTranscript', () => {
|
||||
resolve(transcription);
|
||||
});
|
||||
|
||||
session
|
||||
.start({
|
||||
transcription_config: {
|
||||
language: 'en',
|
||||
operating_point: 'enhanced',
|
||||
enable_partials: true,
|
||||
max_delay: 2,
|
||||
},
|
||||
audio_format: { type: 'file' },
|
||||
})
|
||||
.then(() => {
|
||||
//prepare file stream
|
||||
const fileStream = fs.createReadStream(`${__dirname}/../../data/test_audio.wav`);
|
||||
|
||||
//send it
|
||||
fileStream.on('data', (sample) => {
|
||||
session.sendAudio(sample);
|
||||
});
|
||||
|
||||
//end the session
|
||||
fileStream.on('end', () => {
|
||||
session.stop();
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.info({error}, 'failed to get speechmatics transcript');
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const testNuanceTts = async(logger, getTtsVoices, credentials) => {
|
||||
const voices = await getTtsVoices({vendor: 'nuance', credentials});
|
||||
return voices;
|
||||
@@ -92,8 +156,8 @@ const testGoogleStt = async(logger, credentials) => {
|
||||
};
|
||||
|
||||
const testDeepgramStt = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
const deepgram = new Deepgram(api_key);
|
||||
const {api_key, deepgram_stt_uri, deepgram_stt_use_tls} = credentials;
|
||||
const deepgram = new Deepgram(api_key, deepgram_stt_uri, deepgram_stt_uri && deepgram_stt_use_tls);
|
||||
|
||||
const mimetype = 'audio/wav';
|
||||
const source = {
|
||||
@@ -270,9 +334,10 @@ const testPlayHT = async(logger, synthAudio, credentials) => {
|
||||
{
|
||||
vendor: 'playht',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
language: 'english',
|
||||
voice: 's3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
// Test if playHT can fetch voices
|
||||
@@ -295,7 +360,8 @@ const testRimelabs = async(logger, synthAudio, credentials) => {
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'amber',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -312,7 +378,8 @@ const testWhisper = async(logger, synthAudio, credentials) => {
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'alloy',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -328,7 +395,8 @@ const testDeepgramTTS = async(logger, synthAudio, credentials) => {
|
||||
vendor: 'deepgram',
|
||||
credentials,
|
||||
model: 'aura-asteria-en',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -383,7 +451,8 @@ const testVerbioTts = async(logger, synthAudio, credentials) => {
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'tommy_en-us',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -434,6 +503,20 @@ const testAssemblyStt = async(logger, credentials) => {
|
||||
});
|
||||
};
|
||||
|
||||
const testVoxistStt = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
try {
|
||||
const get = bent('https://api-asr.voxist.com', 'GET', 'json', {
|
||||
'Accept': 'application/json',
|
||||
'x-lvl-key': api_key
|
||||
});
|
||||
await get('/clients');
|
||||
} catch (err) {
|
||||
logger.info({err}, 'failed to get clients from Voxist');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const getSpeechCredential = (credential, logger) => {
|
||||
const {vendor} = credential;
|
||||
logger.info(
|
||||
@@ -509,6 +592,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.deepgram_stt_uri = o.deepgram_stt_uri;
|
||||
obj.deepgram_stt_use_tls = o.deepgram_stt_use_tls;
|
||||
obj.deepgram_tts_uri = o.deepgram_tts_uri;
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
@@ -526,6 +610,10 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
} else if ('soniox' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
} else if ('speechmatics' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.speechmatics_stt_uri = o.speechmatics_stt_uri;
|
||||
} else if ('elevenlabs' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
@@ -537,6 +625,11 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
obj.user_id = o.user_id;
|
||||
obj.voice_engine = o.voice_engine;
|
||||
obj.options = o.options;
|
||||
} else if ('cartesia' === obj.vendor) {
|
||||
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 ('rimelabs' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
@@ -547,9 +640,13 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
obj.auth_token = isObscureKey ? obscureKey(o.auth_token) : o.auth_token;
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
obj.custom_tts_streaming_url = o.custom_tts_streaming_url;
|
||||
} else if ('assemblyai' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
} else if ('voxist' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
} else if ('whisper' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
@@ -613,10 +710,16 @@ async function getLanguagesAndVoicesForVendor(logger, vendor, credential, getTts
|
||||
return await getLanguagesVoicesForRimelabs(credential, getTtsVoices, logger);
|
||||
case 'assemblyai':
|
||||
return await getLanguagesVoicesForAssemblyAI(credential, getTtsVoices, logger);
|
||||
case 'voxist':
|
||||
return await getLanguagesVoicesForVoxist(credential, getTtsVoices, logger);
|
||||
case 'whisper':
|
||||
return await getLanguagesVoicesForWhisper(credential, getTtsVoices, logger);
|
||||
case 'verbio':
|
||||
return await getLanguagesVoicesForVerbio(credential, getTtsVoices, logger);
|
||||
case 'speechmatics':
|
||||
return await getLanguagesVoicesForSpeechmatics(credential, getTtsVoices, logger);
|
||||
case 'cartesia':
|
||||
return await getLanguagesVoicesForCartesia(credential, getTtsVoices, logger);
|
||||
default:
|
||||
logger.info(`invalid vendor ${vendor}, return empty result`);
|
||||
throw new Error(`Invalid vendor ${vendor}`);
|
||||
@@ -698,7 +801,7 @@ async function getLanguagesVoicesForNuane(credential, getTtsVoices, logger) {
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForDeepgram(credential) {
|
||||
return tranform(undefined, SttDeepgramLanguagesVoices, TtsModelDeepgram);
|
||||
return tranform(TtsLanguagesDeepgram, SttDeepgramLanguagesVoices, TtsModelDeepgram);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForIbm(credential, getTtsVoices, logger) {
|
||||
@@ -729,6 +832,10 @@ async function getLanguagesVoicesForSoniox(credential) {
|
||||
return tranform(undefined, SttSonioxLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForSpeechmatics(credential) {
|
||||
return tranform(undefined, SttSpeechmaticsLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForElevenlabs(credential) {
|
||||
if (credential) {
|
||||
const get = bent('https://api.elevenlabs.io', 'GET', 'json', {
|
||||
@@ -738,6 +845,13 @@ async function getLanguagesVoicesForElevenlabs(credential) {
|
||||
const [langResp, voiceResp] = await Promise.all([get('/v1/models'), get('/v1/voices')]);
|
||||
|
||||
const model = langResp.find((m) => m.model_id === credential.model_id);
|
||||
const models = langResp.map((m) => {
|
||||
return {
|
||||
value: m.model_id,
|
||||
name: m.name
|
||||
};
|
||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const languages = model ? model.languages.map((l) => {
|
||||
return {
|
||||
value: l.language_id,
|
||||
@@ -746,27 +860,35 @@ async function getLanguagesVoicesForElevenlabs(credential) {
|
||||
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
|
||||
|
||||
if (languages && languages.length > 0) {
|
||||
// using if condition to avoid \n character in name
|
||||
const voices = voiceResp ? voiceResp.voices.map((v) => {
|
||||
let name = `${v.name}${v.category !== 'premade' ? ` (${v.category})` : ''} -
|
||||
${v.labels.accent ? ` ${v.labels.accent},` : ''}
|
||||
${v.labels.description ? ` ${v.labels.description},` : ''}
|
||||
${v.labels.age ? ` ${v.labels.age},` : ''}
|
||||
${v.labels.gender ? ` ${v.labels.gender},` : ''}
|
||||
${v.labels['use case'] ? ` ${v.labels['use case']},` : ''}
|
||||
`;
|
||||
let name = `${v.name}${v.category !== 'premade' ? ` (${v.category.trim()})` : ''} - (`;
|
||||
if (v.labels.accent) name += `${v.labels.accent}, `;
|
||||
if (v.labels.description) name += `${v.labels.description}, `;
|
||||
if (v.labels.age) name += `${v.labels.age}, `;
|
||||
if (v.labels.gender) name += `${v.labels.gender}, `;
|
||||
if (v.labels['use case']) name += `${v.labels['use case']}, `;
|
||||
|
||||
const lastIndex = name.lastIndexOf(',');
|
||||
if (lastIndex !== -1) {
|
||||
name = name.substring(0, lastIndex);
|
||||
}
|
||||
name += ')';
|
||||
return {
|
||||
value: v.voice_id,
|
||||
name
|
||||
};
|
||||
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
|
||||
languages[0].voices = voices;
|
||||
for (const language of languages) {
|
||||
language.voices = voices;
|
||||
}
|
||||
}
|
||||
return tranform(languages, undefined, TtsModelElevenLabs);
|
||||
return tranform(languages, undefined, models);
|
||||
} else {
|
||||
const voices = TtsElevenlabsLanguagesVoices[0].voices;
|
||||
for (const language of TtsElevenlabsLanguagesVoices) {
|
||||
language.voices = voices;
|
||||
}
|
||||
return tranform(TtsElevenlabsLanguagesVoices, undefined, TtsModelElevenLabs);
|
||||
}
|
||||
}
|
||||
@@ -796,42 +918,67 @@ const fetchLayHTVoices = async(credential) => {
|
||||
|
||||
async function getLanguagesVoicesForPlayHT(credential) {
|
||||
if (credential) {
|
||||
const {voice_engine} = credential;
|
||||
const [cloned_voice, voices] = await fetchLayHTVoices(credential);
|
||||
const list_voices = [...cloned_voice, ...voices];
|
||||
|
||||
const buildVoice = (d) => {
|
||||
let name = `${d.name} -${concat(d.accent)}${concat(d.age)}${concat(d.gender)}${concat(d.loudness)}` +
|
||||
let name = `${d.name} - (${concat(d.accent)}${concat(d.age)}${concat(d.gender)}${concat(d.loudness)}` +
|
||||
`${concat(d.style)}${concat(d.tempo)}${concat(d.texture)}` ;
|
||||
name = name.endsWith(',') ? name.trim().slice(0, -1) : name;
|
||||
name += !d.language_code ? ' - Custom Voice' : '';
|
||||
name += ')';
|
||||
name = name.replaceAll('( ', '(');
|
||||
|
||||
return {
|
||||
value: `${d.id}`,
|
||||
name
|
||||
};
|
||||
};
|
||||
|
||||
const ttsVoices = list_voices.reduce((acc, voice) => {
|
||||
if (!credential.voice_engine.includes(voice.voice_engine)) {
|
||||
const buildPlay30Payload = () => {
|
||||
// PlayHT3.0 can play different languages with differrent voice.
|
||||
// all voices will be added to english language by default and orther langauges will get voices from english.
|
||||
const ttsVoices = ttsLanguagesPlayHt.map((l) => ({
|
||||
...l,
|
||||
voices: l.value === 'english' ? list_voices.map((v) => buildVoice(v)) : []
|
||||
}));
|
||||
return tranform(ttsVoices, undefined, TtsModelPlayHT);
|
||||
};
|
||||
|
||||
const buildPayload = () => {
|
||||
const ttsVoices = list_voices.reduce((acc, voice) => {
|
||||
if (!voice_engine.includes(voice.voice_engine)) {
|
||||
return acc;
|
||||
}
|
||||
const languageCode = voice.language_code;
|
||||
// custom voice does not have language code
|
||||
if (!languageCode) {
|
||||
voice.language_code = 'en';
|
||||
voice.language = 'Custom-English';
|
||||
}
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push(buildVoice(voice));
|
||||
} else {
|
||||
acc.push({
|
||||
value: voice.language_code,
|
||||
name: voice.language,
|
||||
voices: [buildVoice(voice)]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
const languageCode = voice.language_code;
|
||||
// custom voice does not have language code
|
||||
if (!languageCode) {
|
||||
voice.language_code = 'en';
|
||||
voice.language = 'Custom-English';
|
||||
}
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push(buildVoice(voice));
|
||||
} else {
|
||||
acc.push({
|
||||
value: voice.language_code,
|
||||
name: voice.language,
|
||||
voices: [buildVoice(voice)]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return tranform(ttsVoices, undefined, TtsModelPlayHT);
|
||||
}, []);
|
||||
return tranform(ttsVoices, undefined, TtsModelPlayHT);
|
||||
};
|
||||
|
||||
switch (voice_engine) {
|
||||
case 'Play3.0':
|
||||
return buildPlay30Payload();
|
||||
|
||||
default:
|
||||
return buildPayload();
|
||||
}
|
||||
}
|
||||
return tranform(TtsPlayHtLanguagesVoices, undefined, TtsModelPlayHT);
|
||||
}
|
||||
@@ -841,19 +988,17 @@ async function getLanguagesVoicesForRimelabs(credential) {
|
||||
const get = bent('https://users.rime.ai', 'GET', 'json', {
|
||||
'Accept': 'application/json'
|
||||
});
|
||||
const voices = await get('/data/voices/all.json');
|
||||
let selectedVoices = model_id ? voices[model_id] : Object.values(voices).reduce((acc, val) => [...acc, ...val], []);
|
||||
selectedVoices = selectedVoices.map((v) => ({
|
||||
name: v.charAt(0).toUpperCase() + v.slice(1),
|
||||
value: v
|
||||
const voices = await get('/data/voices/all-v2.json');
|
||||
const modelVoices = model_id ? voices[model_id] :
|
||||
Object.keys(voices).length > 0 ? voices[Object.keys(voices)[0]] : [];
|
||||
const ttsVoices = Object.entries(modelVoices).map(([key, voices]) => ({
|
||||
value: key,
|
||||
name: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
voices: voices.map((v) => ({
|
||||
name: v.charAt(0).toUpperCase() + v.slice(1),
|
||||
value: v
|
||||
}))
|
||||
}));
|
||||
const ttsVoices = [
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English (US)',
|
||||
voices: selectedVoices
|
||||
}
|
||||
];
|
||||
return tranform(ttsVoices, undefined, TtsModelRimelabs);
|
||||
}
|
||||
|
||||
@@ -861,21 +1006,28 @@ async function getLanguagesVoicesForAssemblyAI(credential) {
|
||||
return tranform(undefined, SttAssemblyaiLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForVoxist(credential) {
|
||||
return tranform(undefined, SttVoxistLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForWhisper(credential) {
|
||||
return tranform(TtsWhisperLanguagesVoices, undefined, TtsModelWhisper);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForVerbio(credentials, getTtsVoices, logger) {
|
||||
const stt = SttVerbioLanguagesVoices.reduce((acc, v) => {
|
||||
if (!v.version || credentials.engine_version === v.version) {
|
||||
if (!v.version || (credentials && credentials.engine_version === v.version)) {
|
||||
acc.push(v);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
try {
|
||||
const data = await getTtsVoices({vendor: 'verbio', credentials});
|
||||
const voices = parseVerbioLanguagesVoices(data);
|
||||
return tranform(voices, stt, undefined);
|
||||
if (credentials) {
|
||||
const data = await getTtsVoices({vendor: 'verbio', credentials});
|
||||
const voices = parseVerbioLanguagesVoices(data);
|
||||
return tranform(voices, stt, undefined);
|
||||
}
|
||||
return tranform(TtsVerbioLanguagesVoices, stt, undefined);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'there is error while fetching verbio speech voices');
|
||||
return tranform(TtsVerbioLanguagesVoices, stt, undefined);
|
||||
@@ -945,7 +1097,7 @@ function parseAwsLanguagesVoices(data) {
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.Id,
|
||||
name: `(${voice.Gender}) ${voice.Name}`
|
||||
name: `${voice.Name} (${voice.Gender})`
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
@@ -953,7 +1105,7 @@ function parseAwsLanguagesVoices(data) {
|
||||
name: voice.LanguageName,
|
||||
voices: [{
|
||||
value: voice.Id,
|
||||
name: `(${voice.Gender}) ${voice.Name}`
|
||||
name: `${voice.Name} (${voice.Gender})`
|
||||
}]
|
||||
});
|
||||
}
|
||||
@@ -1010,7 +1162,7 @@ function parseMicrosoftLanguagesVoices(data) {
|
||||
}
|
||||
|
||||
function parseVerbioLanguagesVoices(data) {
|
||||
return data.reduce((acc, voice) => {
|
||||
return data.voices.reduce((acc, voice) => {
|
||||
const languageCode = voice.language;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
@@ -1032,6 +1184,95 @@ function parseVerbioLanguagesVoices(data) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
const fetchCartesiaVoices = async(credential) => {
|
||||
if (credential) {
|
||||
const get = bent('https://api.cartesia.ai', 'GET', 'json', {
|
||||
'X-API-Key' : credential.api_key,
|
||||
'Cartesia-Version': '2024-06-10',
|
||||
'Accept': 'application/json'
|
||||
});
|
||||
|
||||
const voices = await get('/voices');
|
||||
return voices;
|
||||
}
|
||||
};
|
||||
|
||||
const testCartesia = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio(
|
||||
{
|
||||
increment: () => {},
|
||||
histogram: () => {}
|
||||
},
|
||||
{
|
||||
vendor: 'cartesia',
|
||||
credentials,
|
||||
language: 'en',
|
||||
voice: '694f9389-aac1-45b6-b726-9d9369183238',
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
// Test if Cartesia can fetch voices
|
||||
await fetchCartesiaVoices(credentials);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synth cartesia returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
async function getLanguagesVoicesForCartesia(credential) {
|
||||
if (credential) {
|
||||
const {model_id} = credential;
|
||||
const {languages} = ttsModelCartesia.find((m) => m.value === model_id);
|
||||
const voices = await fetchCartesiaVoices(credential);
|
||||
|
||||
const buildVoice = (d) => (
|
||||
{
|
||||
value: `${d.id}`,
|
||||
name: `${d.name} - ${d.description}`
|
||||
});
|
||||
const languageMap = {
|
||||
en: 'English',
|
||||
fr: 'French',
|
||||
de: 'German',
|
||||
es: 'Spanish',
|
||||
pt: 'Portuguese',
|
||||
zh: 'Chinese',
|
||||
ja: 'Japanese',
|
||||
hi: 'Hindi',
|
||||
it: 'Italian',
|
||||
ko: 'Korean',
|
||||
nl: 'Dutch',
|
||||
pl: 'Polish',
|
||||
ru: 'Russian',
|
||||
sv: 'Swedish',
|
||||
tr: 'Turkish',
|
||||
};
|
||||
const ttsVoices = voices.reduce((acc, voice) => {
|
||||
if (!languages.includes(voice.language)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const languageCode = voice.language;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push(buildVoice(voice));
|
||||
} else {
|
||||
acc.push({
|
||||
value: languageCode,
|
||||
name: languageMap[languageCode],
|
||||
voices: [buildVoice(voice)]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return tranform(ttsVoices, undefined, TtsModelCartesia);
|
||||
}
|
||||
return tranform(ttsCartesia, undefined, TtsModelCartesia);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testGoogleTts,
|
||||
testGoogleStt,
|
||||
@@ -1057,5 +1298,8 @@ module.exports = {
|
||||
testWhisper,
|
||||
testVerbioTts,
|
||||
testVerbioStt,
|
||||
getLanguagesAndVoicesForVendor
|
||||
getLanguagesAndVoicesForVendor,
|
||||
testSpeechmaticsStt,
|
||||
testCartesia,
|
||||
testVoxistStt
|
||||
};
|
||||
|
||||
3141
package-lock.json
generated
3141
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.3",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "NODE_ENV=test APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall K8S=true K8S_FEATURE_SERVER_SERVICE_NAME=127.0.0.1 K8S_FEATURE_SERVER_SERVICE_PORT=3100 node test/ ",
|
||||
"test:encrypt-decrypt": "ENCRYPTION_SECRET=12345 node --test ./test/encrypt-decrypt.test.js",
|
||||
"integration-test": "NODE_ENV=test JAMBONES_AUTH_USE_JWT=1 JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
|
||||
"upgrade-db": "node ./db/upgrade-jambonz-db.js",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
@@ -28,8 +29,8 @@
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/lamejs": "^1.2.2",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.9",
|
||||
"@jambonz/speech-utils": "^0.1.11",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.10",
|
||||
"@jambonz/speech-utils": "^0.2.3",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.72",
|
||||
"@soniox/soniox-node": "^1.2.2",
|
||||
@@ -46,12 +47,14 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mailgun.js": "^10.2.1",
|
||||
"microsoft-cognitiveservices-speech-sdk": "1.36.0",
|
||||
"mysql2": "^3.9.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.11.0",
|
||||
"nocache": "4.0.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
"pino": "^8.20.0",
|
||||
"short-uuid": "^4.2.2",
|
||||
"speechmatics": "^4.0.0",
|
||||
"stream-buffers": "^3.0.2",
|
||||
"stripe": "^14.24.0",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
|
||||
@@ -152,7 +152,9 @@ test('account tests', async(t) => {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
console.log(result);
|
||||
t.ok(result.name === 'daveh' , 'successfully retrieved account by sid');
|
||||
t.ok(result.enable_debug_log === 0 , 'enable_debug_log default value ok');
|
||||
|
||||
/* update account with account level token */
|
||||
result = await request.put(`/Accounts/${sid}`, {
|
||||
@@ -177,8 +179,8 @@ test('account tests', async(t) => {
|
||||
name: 'recordings',
|
||||
access_key_id: 'access_key_id',
|
||||
secret_access_key: 'secret access key'
|
||||
}
|
||||
|
||||
},
|
||||
enable_debug_log: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated account using account level token');
|
||||
@@ -194,6 +196,7 @@ test('account tests', async(t) => {
|
||||
t.ok(result.bucket_credential.access_key_id === 'access_key_id', 'bucket_access_key_id was updated');
|
||||
t.ok(result.record_all_calls === 1, 'record_all_calls was updated');
|
||||
t.ok(result.record_format === 'wav', 'record_format was updated');
|
||||
t.ok(result.enable_debug_log, 'enable_debug_log was updated');
|
||||
|
||||
/* verify that account level api key last_used was updated*/
|
||||
result = await request.get(`/Accounts/${sid}/ApiKeys`, {
|
||||
|
||||
@@ -121,6 +121,25 @@ test('application tests', async(t) => {
|
||||
let app_json = JSON.parse(result.app_json);
|
||||
t.ok(app_json[0].verb === 'play', 'successfully retrieved app_json from application')
|
||||
|
||||
/* query one application by name*/
|
||||
result = await request.get(`/Applications`, {
|
||||
qs : {
|
||||
name: 'daveh'
|
||||
},
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.length === 1 && result[0].name === 'daveh', 'successfully queried application by name');
|
||||
|
||||
/* query application with invalid name*/
|
||||
result = await request.get(`/Applications`, {
|
||||
qs : {
|
||||
name: 'daveh-invalid'
|
||||
},
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.length === 0, 'successfully queried application by invalid name, no results found');
|
||||
|
||||
/* update applications */
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
|
||||
132
test/encrypt-decrypt.test.js
Normal file
132
test/encrypt-decrypt.test.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const assert = require('node:assert');
|
||||
const { describe, it, beforeEach, before } = require('node:test');
|
||||
const { obscureBucketCredentialsSensitiveData, isObscureKey, obscureKey } = require('../lib/utils/encrypt-decrypt');
|
||||
const { randomUUID } = require('node:crypto');
|
||||
|
||||
let bucket_credential = {};
|
||||
let service_key = {};
|
||||
|
||||
describe('encrypt-decrypt test suite', { skip: false }, () => {
|
||||
before(() => {
|
||||
process.env.ENCRYPTION_SECRET = "12345";
|
||||
});
|
||||
beforeEach(() => {
|
||||
service_key = {
|
||||
"type": "service_account",
|
||||
"project_id": "voice-gateway",
|
||||
"private_key_id": randomUUID(),
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nJ6T2sQmBAD8TBm0frkGeyw0FA0aVAASVBJcwggSjBgEAAoabAQDUbiVwgufJm9R3\nv7YpECxRnQgHduX8jnbjtubi6FVqqVx78W3NM/gFNJFBC7NXtpsslqefKM6SmlkK\nNP6XqXy/ppXO8u00se7+cOFhsS6crncnsCeIYPxLFPV8P4BjExi7v88RBdektLeV\nX6sRWhxiYeWe+ORxkyC0KR4IKZjHt7ZHrg0kNQyuNx1KOhJnN3rRUkKSP2zozd1c\n3V+4EfpZjGmlQegXNHzkwUjvTOe6nuyxynWe3smjsSw/3RQda5m674Kh9tnVWEiX\n5KWnfRbWDdEBp5azzOAdeSR5W+qQfS0Jo6blREDQxWfMNmut87m81gmn+DKMhq6k\nV0JVRQStAgMBAAECggEAAf7AsAdI24zUdXkZv98gEuC4S10e2Luoz6Yvm41V3sP6\nWx0mdgY7RB9RW8U4WPnu3GhPGGJj03SyHUuI8Ix15MNM9WGDAaV3S+kRb1LqChLO\nCoNSO/qYPPg4t1iQ9+s7sWTnM93MAXjujmSveHJd7+MrUQOxOdPjB7I/ozMkBXBb\nWYsBIfeOG/7DsC4N4V/hKVXAq9NekGQv85yCUPR/DpuG9vqpztXwaSC1Wihntlu3\nNJYmMave4PbO3OxAekBl70WmukERZo7ksR+34WWse4HXlphaUVbpvnbQdGT1EzXW\nZukanqpfkIwrHme2Ko5NdP0C54pRhg6kpmWszVUMQQKBgQD6yWMuuwdPuFLg0wc2\nnDUeyHZSq/SEMSrb1wNXhL3qhaL2tCjBZwG9CHDuFMhqDLU5F36mEdAsM5MHCzyC\nTJ7VbqvCFz69SRt1CVp76Hu4HO/1Nhxm1GhF+NKSDIbnUg3o4DaC7EUtLpqYXcWj\nsXHEqVEhkrNVQ/JOIfJr42LDfQKBgQDM0U3ghGg9ufe9mb0dOonwtJA/ay/pd08Q\nyq3ue9C3yoQiNf28bP3AGKIjhA6wtd0KTSkQs6ktabGHIM8o/eTrMAMQllKh/3xe\nON7iND8Xz2GFMuIraQ7Mq9RvYWiqqIkVg1GQfJmiQ9wcmGj2PHy25LfjBXfHAYqK\nQ++P/i+s8QKBgD0pRi4MYNEZY+T+skCoQfA69VheJWjj0M8ClgcPEX4Tj1XZRCM+\nqtbeKyR1Hxd19/BvgWyg5YMSJOZP4Dbq1sW4ktzn7F4faTnWySF05k9Vh1PnGXAe\nlzuRXlFOCsx5X3kOzVyKoKhPOFa2b8/nI5bRsD6e12uRAZP6hXO4ZcrFAoGABVJ/\nCpGGP+xgMq4XCvZldTrL8MnxQcjW5iHOKT9QaiY6DsWGZWoTofVB6VhaJV9kcgsV\nQRjaEZMIiPFiULdgRnhF7B1r4kfITI5/xDMFXLIH37U1yVj+iHUCnS5T0PN2NHfo\nG7ARMfU/eALB33ws5XfGC4Et3p78oaEoTX6WcJECgYEAhysSt4qieGRSXn24V0+u\ny/ubU4dysn4SFe8BB4bjgYa8v+6VwucU+nnU4wOwykEgJmzN/ukzpvFN0CkN8eAN\n8xwtjBX9Zc1S90Wf7/7IrQGlUnsSFpDh5TW+oCqVo8JK7UGxJHR1mCvJmYVmSk3c\nD4AMvJ2x/Z5d9NKIAzdET4o=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "voicegatewayteam@voice-gateway.iam.gserviceaccount.com",
|
||||
"client_id": "333827616171177028875",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/cognigyvoicegatewayteam%40cognigy-voice-gateway.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
};
|
||||
bucket_credential = {
|
||||
"vendor": "google",
|
||||
"service_key": "",
|
||||
"connection_string": "",
|
||||
"secret_access_key": ""
|
||||
}
|
||||
});
|
||||
|
||||
describe('obscureBucketCredentialsSensitiveData', { skip: false }, () => {
|
||||
it('should obscure "private_key" - vendor: google', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = obscureBucketCredentialsSensitiveData(bucket_credential);
|
||||
const googleCredentials = JSON.parse(result.service_key);
|
||||
assert.strictEqual(googleCredentials.private_key, '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
|
||||
assert.strictEqual(googleCredentials.private_key_id, googleCredentials.private_key_id);
|
||||
assert.strictEqual(googleCredentials.project_id, googleCredentials.project_id);
|
||||
assert.strictEqual(googleCredentials.client_email, googleCredentials.client_email);
|
||||
assert.strictEqual(googleCredentials.client_id, googleCredentials.client_id);
|
||||
assert.strictEqual(googleCredentials.auth_uri, googleCredentials.auth_uri);
|
||||
assert.strictEqual(googleCredentials.token_uri, googleCredentials.token_uri);
|
||||
});
|
||||
|
||||
it('should skip obscure since it is already obscured. vendor: google - "private key"', { skip: false }, () => {
|
||||
service_key.private_key = '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = obscureBucketCredentialsSensitiveData(bucket_credential);
|
||||
const googleCredentials = JSON.parse(result.service_key);
|
||||
assert.strictEqual(googleCredentials.private_key, '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
|
||||
assert.strictEqual(googleCredentials.private_key_id, googleCredentials.private_key_id);
|
||||
assert.strictEqual(googleCredentials.project_id, googleCredentials.project_id);
|
||||
assert.strictEqual(googleCredentials.client_email, googleCredentials.client_email);
|
||||
assert.strictEqual(googleCredentials.client_id, googleCredentials.client_id);
|
||||
assert.strictEqual(googleCredentials.auth_uri, googleCredentials.auth_uri);
|
||||
assert.strictEqual(googleCredentials.token_uri, googleCredentials.token_uri);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObscureKey', { skip: false }, () => {
|
||||
it('vendor: google - should return true', { skip: false }, () => {
|
||||
service_key.private_key = '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
it('vendor: google - should return false', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
it('vendor: aws_s3 - should return true', { skip: false }, () => {
|
||||
const obscuredKey = "J6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "aws_s3",
|
||||
"secret_access_key": obscuredKey,
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
it('vendor: aws_s3 - should return false', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "aws_s3",
|
||||
"service_key": "EFEU2fhcbqiw3211ffw3f1kezhcbqiw3211ffw3f",
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
it('vendor: azure - should return true', { skip: false }, () => {
|
||||
const obscuredKey = 'https:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "azure",
|
||||
"connection_string": obscuredKey,
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
it('vendor: azure - should return false', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "azure",
|
||||
connection_string: 'https://cognigydevstorage.blob.core.windows.net/voicegateway-test?sp=rw&st=2023-09-10T13:35:44Z&se=2023-09-11T21:35:44Z&spr=https&sv=2022-11-02&sr=c&sig=9WN8Bg5UMOvnV1h1cJpCnTUG%2FnonTbRZ1Q1rbKnDUl4%3D',
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,53 @@ test('sip gateway tests', async(t) => {
|
||||
let result;
|
||||
const voip_carrier_sid = await createVoipCarrier(request);
|
||||
|
||||
/* add a invalid sip gateway */
|
||||
const STORED_JAMBONZ_MIN_GATEWAY_NETMASK = process.env.JAMBONZ_MIN_GATEWAY_NETMASK;
|
||||
process.env.JAMBONZ_MIN_GATEWAY_NETMASK = 24;
|
||||
|
||||
result = await request.post('/SipGateways', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
simple: false,
|
||||
body: {
|
||||
voip_carrier_sid,
|
||||
ipv4: '1.2.3.4',
|
||||
netmask: 1,
|
||||
inbound: true,
|
||||
outbound: true,
|
||||
protocol: 'tcp'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 400, 'successfully created sip gateway ');
|
||||
|
||||
result = await request.post('/SipGateways', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
voip_carrier_sid,
|
||||
ipv4: '1.2.3.4',
|
||||
netmask: 24,
|
||||
inbound: true,
|
||||
outbound: true,
|
||||
protocol: 'tcp'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created sip gateway ');
|
||||
|
||||
process.env.JAMBONZ_MIN_GATEWAY_NETMASK = STORED_JAMBONZ_MIN_GATEWAY_NETMASK;
|
||||
|
||||
/* delete sip gateways */
|
||||
result = await request.delete(`/SipGateways/${result.body.sid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
json: true,
|
||||
auth: authAdmin
|
||||
});
|
||||
//console.log(`result: ${JSON.stringify(result)}`);
|
||||
t.ok(result.statusCode === 204, 'successfully deleted sip gateway');
|
||||
|
||||
/* add a sip gateway */
|
||||
result = await request.post('/SipGateways', {
|
||||
resolveWithFullResponse: true,
|
||||
|
||||
@@ -371,7 +371,8 @@ test('speech credentials tests', async(t) => {
|
||||
vendor: 'deepgram',
|
||||
use_for_stt: true,
|
||||
deepgram_stt_uri: "127.0.0.1:50002",
|
||||
deepgram_stt_use_tls: true
|
||||
deepgram_stt_use_tls: true,
|
||||
deepgram_tts_uri: 'https://server.com'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for deepgram');
|
||||
@@ -386,6 +387,7 @@ test('speech credentials tests', async(t) => {
|
||||
t.ok(result.statusCode === 200, 'successfully get speech credential for deepgram');
|
||||
t.ok(result.body.deepgram_stt_uri === '127.0.0.1:50002', "deepgram_stt_uri is correct for deepgram");
|
||||
t.ok(result.body.deepgram_stt_use_tls === true, "deepgram_stt_use_tls is correct for deepgram");
|
||||
t.ok(result.body.deepgram_tts_uri === 'https://server.com', "deepgram_tts_uri is correct for deepgram")
|
||||
|
||||
result = await request.put(`/Accounts/${account_sid}/SpeechCredentials/${dg_sid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
@@ -395,7 +397,8 @@ test('speech credentials tests', async(t) => {
|
||||
vendor: 'deepgram',
|
||||
use_for_stt: true,
|
||||
deepgram_stt_uri: "127.0.0.2:50002",
|
||||
deepgram_stt_use_tls: false
|
||||
deepgram_stt_use_tls: false,
|
||||
deepgram_tts_uri: 'https://server2.com'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated speech credential for deepgram onprem');
|
||||
@@ -409,6 +412,7 @@ test('speech credentials tests', async(t) => {
|
||||
t.ok(result.statusCode === 200, 'successfully get speech credential for deepgram onprem');
|
||||
t.ok(result.body.deepgram_stt_uri === '127.0.0.2:50002', "deepgram_stt_uri is correct for deepgram onprem");
|
||||
t.ok(result.body.deepgram_stt_use_tls === false, "deepgram_stt_use_tls is correct for deepgram onprem");
|
||||
t.ok(result.body.deepgram_tts_uri === 'https://server2.com', "deepgram_tts_uri is correct for deepgram onprem");
|
||||
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${dg_sid}`, {
|
||||
auth: authUser,
|
||||
@@ -528,6 +532,39 @@ test('speech credentials tests', async(t) => {
|
||||
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
|
||||
}
|
||||
|
||||
/* add a credential for Speechmatics */
|
||||
if (process.env.SPEECHMATICS_API_KEY) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'speechmatics',
|
||||
use_for_stt: true,
|
||||
api_key: process.env.SPEECHMATICS_API_KEY,
|
||||
speechmatics_stt_uri: 'eu2.rt.speechmatics.com'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for speechmatics');
|
||||
const ms_sid = result.body.sid;
|
||||
|
||||
/* test the speech credential */
|
||||
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}/test`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
});
|
||||
console.log(JSON.stringify(result));
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for speechmatics');
|
||||
|
||||
/* delete the credential */
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
|
||||
auth: authUser,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
|
||||
}
|
||||
|
||||
/* add a credential for nvidia */
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
@@ -743,6 +780,27 @@ test('speech credentials tests', async(t) => {
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
|
||||
|
||||
/* add a credential for Voxist */
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'voxist',
|
||||
use_for_stt: true,
|
||||
api_key: "APIKEY"
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for Voxist');
|
||||
const voxistSid = result.body.sid;
|
||||
|
||||
/* delete the credential */
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${voxistSid}`, {
|
||||
auth: authUser,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
|
||||
|
||||
/* add a credential for aws polly by roleArn */
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
@@ -935,6 +993,15 @@ test('speech credentials tests', async(t) => {
|
||||
});
|
||||
t.ok(result.body.stt.length !== 0, 'successfully get assemblyai supported languages and voices');
|
||||
|
||||
/* Check voxist supportedLanguagesAndVoices */
|
||||
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=voxist`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.body.stt.length !== 0, 'successfully get voxist supported languages and voices');
|
||||
|
||||
/* Check whisper supportedLanguagesAndVoices */
|
||||
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=whisper`, {
|
||||
resolveWithFullResponse: true,
|
||||
|
||||
@@ -16,7 +16,9 @@ test('system information test', async(t) => {
|
||||
body: {
|
||||
domain_name: 'test.com',
|
||||
sip_domain_name: 'sip.test.com',
|
||||
monitoring_domain_name: 'monitor.test.com'
|
||||
monitoring_domain_name: 'monitor.test.com',
|
||||
private_network_cidr: '192.168.1.0/24, 10.10.100.1',
|
||||
log_level: 'info'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created system information ');
|
||||
@@ -24,6 +26,8 @@ test('system information test', async(t) => {
|
||||
t.ok(body.domain_name === 'test.com', 'added domain_name ok');
|
||||
t.ok(body.sip_domain_name === 'sip.test.com', 'added sip_domain_name ok');
|
||||
t.ok(body.monitoring_domain_name === 'monitor.test.com', 'added monitoring_domain_name ok');
|
||||
t.ok(body.private_network_cidr === '192.168.1.0/24, 10.10.100.1', 'added private_network_cidr ok');
|
||||
t.ok(body.log_level === 'info', 'added log_level ok');
|
||||
|
||||
result = await request.get('/SystemInformation', {
|
||||
auth: authAdmin,
|
||||
@@ -32,6 +36,8 @@ test('system information test', async(t) => {
|
||||
t.ok(result.domain_name === 'test.com', 'get domain_name ok');
|
||||
t.ok(result.sip_domain_name === 'sip.test.com', 'get sip_domain_name ok');
|
||||
t.ok(result.monitoring_domain_name === 'monitor.test.com', 'get monitoring_domain_name ok');
|
||||
t.ok(result.private_network_cidr === '192.168.1.0/24, 10.10.100.1', 'get private_network_cidr ok');
|
||||
t.ok(result.log_level === 'info', 'added log_level ok');
|
||||
|
||||
result = await request.post('/SystemInformation', {
|
||||
resolveWithFullResponse: true,
|
||||
@@ -40,7 +46,9 @@ test('system information test', async(t) => {
|
||||
body: {
|
||||
domain_name: 'test1.com',
|
||||
sip_domain_name: 'sip1.test.com',
|
||||
monitoring_domain_name: 'monitor1.test.com'
|
||||
monitoring_domain_name: 'monitor1.test.com',
|
||||
private_network_cidr: '',
|
||||
log_level: 'debug'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully updated system information ');
|
||||
@@ -48,6 +56,8 @@ test('system information test', async(t) => {
|
||||
t.ok(body.domain_name === 'test1.com', 'updated domain_name ok');
|
||||
t.ok(body.sip_domain_name === 'sip1.test.com', 'updated sip_domain_name ok');
|
||||
t.ok(body.monitoring_domain_name === 'monitor1.test.com', 'updated monitoring_domain_name ok');
|
||||
t.ok(body.private_network_cidr === '', 'updated private_network_cidr ok');
|
||||
t.ok(body.log_level === 'debug', 'updated log_level ok');
|
||||
|
||||
result = await request.get('/SystemInformation', {
|
||||
auth: authAdmin,
|
||||
@@ -56,6 +66,7 @@ test('system information test', async(t) => {
|
||||
t.ok(result.domain_name === 'test1.com', 'get domain_name ok');
|
||||
t.ok(result.sip_domain_name === 'sip1.test.com', 'get sip_domain_name ok');
|
||||
t.ok(result.monitoring_domain_name === 'monitor1.test.com', 'get monitoring_domain_name ok');
|
||||
t.ok(result.log_level === 'debug', 'updated log_level ok');
|
||||
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -24,16 +24,24 @@ function makeSynthKey({account_sid = '', vendor, language, voice, engine = '', t
|
||||
test('tts-cache', async(t) => {
|
||||
const app = require('../app');
|
||||
try {
|
||||
// clear cache to start
|
||||
let result = await request.delete('/TtsCache', {
|
||||
auth: authAdmin,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully purged cache for start of test');
|
||||
|
||||
// create caches
|
||||
const minRecords = 8;
|
||||
for (const i in Array(minRecords).fill(0)) {
|
||||
await client.set(makeSynthKey({vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
||||
}
|
||||
|
||||
let result = await request.get('/TtsCache', {
|
||||
result = await request.get('/TtsCache', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
//console.log(result);
|
||||
|
||||
t.ok(result.size === minRecords, 'get cache correctly');
|
||||
|
||||
@@ -41,7 +49,7 @@ test('tts-cache', async(t) => {
|
||||
auth: authAdmin,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted application after removing phone number');
|
||||
t.ok(result.statusCode === 204, 'successfully purged cache');
|
||||
|
||||
result = await request.get('/TtsCache', {
|
||||
auth: authAdmin,
|
||||
|
||||
@@ -207,7 +207,8 @@ test('voip carrier tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
name: 'twilio',
|
||||
e164_leading_plus: true
|
||||
e164_leading_plus: true,
|
||||
dtmf_type: 'tones'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created voip carrier for a service provider');
|
||||
@@ -221,6 +222,7 @@ test('voip carrier tests', async(t) => {
|
||||
});
|
||||
//console.log(result.body);
|
||||
t.ok(result.statusCode === 200, 'successfully retrieved voip carrier for a service provider');
|
||||
//console.log(result.body);
|
||||
sid = result.body[0].voip_carrier_sid;
|
||||
|
||||
await deleteObjectBySid(request, '/VoipCarriers', sid);
|
||||
|
||||
Reference in New Issue
Block a user