Compare commits

...

135 Commits

Author SHA1 Message Date
Hoan Luu Huu
1e532212f9 support whisper tts model (#405) 2025-04-22 09:47:13 -04:00
Hoan Luu Huu
92347c26bf fix speechmatic cannot update stt url (#410) 2025-04-22 09:35:02 -04:00
Sam Machin
bc51b60e9b fetch voip carriers where api key doesn't have the service_provdier_sid availible (#412) 2025-04-22 09:34:36 -04:00
Sam Machin
f0ec0a916f Update sip-gateways.js (#421) 2025-04-22 09:30:55 -04:00
rammohan-y
c94f14f27d Added new model Playht Dialog-1-0 model (#423)
https://github.com/jambonz/jambonz-api-server/issues/419
2025-04-22 09:30:18 -04:00
Sam Machin
06873186ac fix the creds test and return correct error (#420) 2025-04-22 07:38:54 -04:00
rammohan-y
956da4334f Support ssl connection to mysql (#414)
* Support ssl connection to mysql
https://github.com/jambonz/jambonz-api-server/issues/413

* using 0/1 for rejectUnauthorized

* added validations for JAMBONES_MYSQL_REJECT_UNAUTHORIZED

* simplied assignment of opts.ssl

* Updated db-helpers to 0.9.11

https://github.com/jambonz/jambonz-feature-server/issues/1151
2025-04-14 08:21:55 -04:00
Hoan Luu Huu
c144758d44 fix: reject if user.provider !== local when login (#408) 2025-04-09 09:53:24 -04:00
rammohan-y
e24f3472ae user should be able to change the temprary password (#407)
Refer to https://github.com/jambonz/jambonz-api-server/issues/406
2025-04-09 09:32:15 -04:00
rammohan-y
4c935c7fda Feat/371 view only user implementation using user_permissions (#381)
* https://github.com/jambonz/jambonz-api-server/issues/371

Implemented view_only permission feature

* calling prepare-permissions in create-test-db.js

* check if there is only 1 permission and if it is VIEW_ONLY then consider user as read-only user

* setting is_view_only flag for view user by userid
2025-04-01 09:29:06 -04:00
Hoan Luu Huu
1c55bad04f support openai stt (#402)
* support openai stt

* wip

* wip

* add stt languages for openai
2025-03-28 10:14:50 -04:00
Hoan Luu Huu
32a2bfcdb5 support cartesia sonic-2 model (#403)
* support cartesia sonic-2 model

* wip

* fix typo

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2025-03-28 09:52:01 -04:00
Hoan Luu Huu
becc1636b7 deepgram milti languages (#397) 2025-03-17 21:10:22 -04:00
Sam Machin
68a9b4226d fix min 25 (#396) 2025-03-11 07:48:04 -04:00
rammohan-y
b154b56064 updated realtimedb-helper to 0.8.13 (#395) 2025-03-10 09:54:24 -04:00
Hoan Luu Huu
556d5c3526 support fetching logs from cloudwatch for call_sid (#393)
* support fetching logs from cloudwatch for call_sid

* wip

* wip

* wip

* ưip

* wip

* fix review comments
2025-03-10 08:31:24 -04:00
Hoan Luu Huu
2ac0da0d14 Should not allow create phone_number on different voip_carrier and account (#394)
* shouldnt create phonenumber on different carrier and account

* wip
2025-03-06 07:40:54 -05:00
Dave Horton
5a02346c71 #390: correct column name is outbound_sip_proxy (#391) 2025-02-24 15:58:34 -05:00
Hoan Luu Huu
d872d9ee87 suport voip carrier sip_proxy (#389)
* suport voip carrier sip_proxy

* fix review comment
2025-02-17 09:46:20 -05:00
Dave Horton
d81a0167cf scripts for import and export of database 2025-02-12 08:28:43 -05:00
Hoan Luu Huu
c7c56d8ea0 update speech utils version (#388) 2025-02-07 08:00:21 -05:00
Hoan Luu Huu
9cfe990bb8 support rimelabs new voices (#387) 2025-02-07 07:22:42 -05:00
Hoan Luu Huu
6c7d2c9074 support rime mistv2 model (#386) 2025-02-06 21:36:34 -05:00
Hoan Luu Huu
73e35c84c5 support voxist stt (#384) 2025-02-05 08:32:36 -05:00
Dave Horton
86d50d94cb optionally send invites to our slack channel on signup (#385) 2025-02-05 00:06:58 -05:00
Hoan Luu Huu
b8f4ad6b27 support fetching elevenlabs models by api-key (#383) 2025-02-03 08:28:48 -05:00
Dave Horton
ad3ec926ee proper version 2025-02-02 17:11:33 -05:00
rammohan-y
66bd9a442c feat/379: added tts property to deepgram response (#380) 2025-01-21 08:15:32 -05:00
Hoan Luu Huu
fa81d179a1 verbio list voice api is changed, fixed (#378) 2025-01-15 06:54:55 -05:00
Dave Horton
fab8a391b7 update deps 2025-01-14 10:46:42 -05:00
Hoan Luu Huu
89288acf6e support custom tts streaming vendor (#377) 2025-01-14 07:14:07 -05:00
Markus Frindt
23cd4408a5 Feat/obscure sensitive bucket credentials (#375)
* obscure sensitive bucket credentials

* npm audit fix

* fix condition

* add test suite encrypt-decrypt.test

* revert docker-compose

* update pipeline

---------

Co-authored-by: mfrindt <m.frindt@cognigy.com>
2025-01-03 07:12:44 -05:00
Dave Horton
ce4618523c correct clients.password col name (#373)
* correct clients.password col name

* fix #372
2024-12-28 16:42:32 -05:00
Hoan Luu Huu
0eb8097e32 support tts cartesia (#370)
* support tts cartesia

* update speech utils

* revert reset password

* revert serve-integration
2024-12-19 09:19:28 -05:00
rammohan-y
8851b3fac0 feat/367 added support for name query parameter for retrieving application (#368)
* feat/367 added support for name query parameter for getting application by name

* feat/367 - Updated test case and modified retrieveAll method to make name independent of service_provider_id and account_sid

* updated query to use WHERE 1=1 to avoid whereFlag variable

* feat/367: removed empty line added accidently
2024-12-18 08:18:48 -05:00
Dave Horton
e080118b6a minor logging changes 2024-12-13 10:47:25 -05:00
rammohan-y
75c27e3f80 feat/982: fixed issue where vendor's API is not called if vendor is defined at service provider level, and also made format of name consistent (#365) 2024-12-12 17:06:45 -05:00
Dave Horton
843980c7f6 #361 (#362)
* #361

* fix typo in db upgrade script

---------

Co-authored-by: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com>
2024-11-26 20:25:25 -05:00
Hoan Luu Huu
f9990da468 allow dub as http updateCall request (#363) 2024-11-14 07:20:10 -05:00
Hoan Luu Huu
e8d5655abb update speech utils version (#360) 2024-11-04 08:07:43 -05:00
Hoan Luu Huu
e908f5830c support add google voice cloning key (#358)
* support add google voice cloning key

* update upgrade db script

* wip
2024-11-04 07:10:42 -05:00
Dave Horton
5c7bac91a8 update to latest speech-utils 2024-10-18 12:29:05 -04:00
Hoan Luu Huu
de250c8d58 support playht3.0 languages (#357)
* support playht3.0 languages

* update speech utils version
2024-10-16 07:34:02 -04:00
Dave Horton
84d83a0a48 playht test failing due to use of incorrect language name en-US (#356) 2024-10-14 20:58:23 -04:00
Hoan Luu Huu
b5bede7a08 add support for speechmatics languages and voices (#355) 2024-10-11 19:54:22 -04:00
Hoan Luu Huu
6e779f6744 support stt speechmatics (#353)
* support stt speechmatics

* support speechmatics region authentication

* update testcase for speechmatics_stt_uri
2024-10-11 09:17:40 -04:00
Hoan Luu Huu
77b9ca4cba update speech version 0.1.18 (#354) 2024-10-11 08:42:13 -04:00
Hoan Luu Huu
0451b6982c Merge pull request #350 from jambonz/feat/playht30
support playht3.0
2024-10-10 10:40:57 +07:00
Hoan Luu Huu
71adc577e9 Merge branch 'main' into feat/playht30 2024-10-10 10:38:13 +07:00
Hoan Luu Huu
e8b32103fe update speech version (#352) 2024-10-09 19:44:43 -04:00
Hoan Luu Huu
57d8d0a02c allow system information contains log level and account has enable_debug_log (#351)
* allow system information contains log level and account has enable_debug_log

* update upgrade db script
2024-10-07 09:52:11 -04:00
Quan HL
a41760fa9f PlayHT version 3.0 support PlayHt2.0 voices 2024-10-03 13:00:42 +07:00
Quan HL
c6bae80a03 support playht3.0 2024-09-27 11:13:59 +07:00
Dave Horton
4cddbd83a1 update to version of realtime-db with fix for expires (#349) 2024-09-18 08:24:47 -04:00
Dave Horton
6275aac341 bump version 2024-09-04 13:34:52 +01:00
Hoan Luu Huu
52de41c9bc support configuration to limit minimum value sipgatewa netmask can be used (#344) 2024-08-19 21:44:15 -04:00
Dave Horton
ed71abd675 added private_newtwork_cidr to system_information table (#341)
* added private_newtwork_cidr to system_information table

* db schema upgrade to add system_information.private_network_cidr in 0.9.2

* increase size of system_information.private_network_cidr to varchar(8192)
2024-08-18 12:49:06 -04:00
Hoan Luu Huu
2d2b98dab5 Feat/deepgram tts onprem (#338)
* support deepgram onpremise

* wip

* update speech utils version

* install docker in ci
2024-08-07 07:24:58 -04:00
Hoan Luu Huu
7553e2b617 update mysql2 version (#339) 2024-08-06 11:42:33 -04:00
Hoan Luu Huu
b921cab867 Support elevenlabs 2.5 (#336)
* update elevenlab model

* wip

* wip

* wip
2024-07-24 13:04:12 -04:00
Hoan Luu Huu
48e1a72ef3 support use sips scheme for outbound tls gateway (#332)
* support use sips scheme for outbound tls gateway

* support use sips scheme for outbound tls gateway

* update license
2024-06-15 09:17:05 -04:00
Hoan Luu Huu
4337a55a27 update getAwsAuthToken to use parameters as object (#330)
* update getAwsAuthToken to use parameters as object

* update speech utils version
2024-06-15 08:10:58 -04:00
Hoan Luu Huu
6041b1d595 fix cannot update verbio engine_version (#327) 2024-06-04 09:48:24 -04:00
Hoan Luu Huu
d33d0aa519 support verbio speech (#323)
* support verbio speech

* wip

* update speech version

* update verb specification
2024-05-29 07:35:40 -04:00
Dave Horton
ffe9cb23eb update speech-utils (#325) 2024-05-28 18:24:57 -04:00
Hoan Luu Huu
dbbc894832 support list conference (#321)
* support list conference

* add test case

* fix conference action requires tag

* fix failing test case
2024-05-28 10:31:16 -04:00
Hoan Luu Huu
82c16380f5 fix Speech credential test for azure (#322) 2024-05-14 06:56:06 -04:00
Hoan Luu Huu
c0fab2880b fix cannot send multipart to aws due to min size (#319) 2024-05-03 07:37:38 -04:00
Hoan Luu Huu
ce2fa392a4 support aws speech by roleArn (#313)
* support aws speech by roleArn

* support 3 types of aws  credentials

* wip

* wip

* update speech util version
2024-05-02 07:57:22 -04:00
Hoan Luu Huu
3b47162d13 Feat/record with pipeline (#318)
* use pipeline for nodejs streams

* use pipeline for nodejs streams
2024-04-30 07:39:24 -04:00
Hoan Luu Huu
b765232d4f api server cannot synthesize text after upgrade latest speech-utils (#317)
* api server cannot synthesize text after upgrade latest speech-utils

* wip

* add testcase for synthesize text

* fix synthesize testcase
2024-04-29 19:48:34 -04:00
Dave Horton
2436bea6ea add support for LCC updateCall with conferenceParticipantState (#296)
* add support for LCC updateCall with conferenceParticipantState

* wip

* wip
2024-04-22 11:06:08 -04:00
Dave Horton
f67abddbd4 bug: attempting to add duplicate dns records on hosted system (#312) 2024-04-19 18:13:27 -04:00
Hoan Luu Huu
39fcb17dec support mod_rimelabs_tts (#310)
* support mod_rimelabs_tts

* update speech utils 0.0.51
2024-04-12 07:25:04 -04:00
Hoan Luu Huu
80418aa7e5 check playht can fetch voices when adding new speech credential (#309)
* check playht can fetch voices when adding new speech credential

* wip

* wip

* wip

* wip
2024-04-12 07:01:13 -04:00
Hoan Luu Huu
b21d10eb3e fetch playht custom voice (#307) 2024-04-09 08:48:18 -04:00
Hoan Luu Huu
7875eb51b9 playht should return list of voice match voice engine configured at speech credentials (#306) 2024-04-09 06:53:17 -04:00
Hoan Luu Huu
e2c1383723 support mod_playht_tts (#304)
* support mod_playht_tts

* wip

* wip

* wip

* wip

* wip

* update speech utils version
2024-04-08 10:21:29 -04:00
Dave Horton
40de2c5945 option_ping was incorrectly removed, adding back (#305) 2024-04-08 08:56:31 -04:00
Dave Horton
3a299bc3ca update to speech utils with azure 1.36.0 (#303) 2024-04-07 17:45:33 -04:00
Dave Horton
70c9407742 update to speech utils with azure 1.36.0 2024-04-07 12:16:55 -04:00
Dave Horton
dba66d58fc back out column addition of -register_use_tls 2024-04-06 13:48:26 -04:00
Dave Horton
0ff3d22faf Revert "feat send options ping for sip gateway (#273)"
This reverts commit a4792a521f.
2024-04-06 13:27:32 -04:00
Hoan Luu Huu
187a428a75 register use tls (#302) 2024-04-04 08:02:29 -04:00
Hoan Luu Huu
a4792a521f feat send options ping for sip gateway (#273)
* feat send options ping for sip gateway

* update upgrade db script to have 8006
2024-03-30 09:14:29 -04:00
Dave Horton
3ac9693735 update speech-utils with fixes for deepgram production api and tts streaming 2024-03-24 08:15:00 -04:00
Dave Horton
3ad54a0e72 update to released deepgram tts voices (#299) 2024-03-13 09:16:12 -04:00
Hoan Luu Huu
bd8fb2f9db remove use_streaming from speech credential (#294)
* remove use_streaming from speech credential

* wip
2024-02-20 08:01:33 -05:00
Dave Horton
32b317ae68 update to latest speech-utils 2024-02-12 21:11:49 -05:00
Hoan Luu Huu
40e8d08727 support deepgram tts onprem (#292)
* support deepgram tts onprem

* wip

* wip

* deepgram disable speech test if api_key is missng
2024-02-12 09:27:13 -05:00
Hoan Luu Huu
256ca440a0 add use_streaming flag for elevenlabs and whisper (#290)
* add use_streaming flag for elevenlabs (not for whisper yet)
---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2024-02-12 09:18:49 -05:00
Markus Frindt
68d73345ef Improve Swagger file, add login route, fix swagger linting (#291)
Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
2024-02-06 12:10:31 -05:00
Hoan Luu Huu
54dd72ff66 fetch list of tts voices from provider (#289)
* fetch list of tts voices from provider

* revert serve-integration

* fix

* fix for aws

* fix for aws

* fix for aws

* update speech-utils version
2024-01-25 12:03:02 -05:00
Dave Horton
832a4e8032 update db-helpers 2024-01-17 13:24:51 -05:00
Hoan Luu Huu
33c3b99e2e update paid account to active if it's in deactivated (#287)
* update paid account to active if it's in deactivated

* fix review comment
2024-01-17 09:20:38 -05:00
Hoan Luu Huu
8b2a2e196e Feat/record upload buffer (#285)
* uploader with buffer for google and azure

* wip

* wip

* wip
2024-01-15 09:51:10 -05:00
Hoan Luu Huu
556717a9a4 enable convert raw to mp3 (#284) 2024-01-11 07:47:41 -05:00
Dave Horton
f2c635268f #283: fix for db upgrade script 2024-01-10 10:11:47 -05:00
Hoan Luu Huu
c8999a5929 fix tts stt langs and voices when credential is provided (#282) 2024-01-09 07:31:33 -05:00
Hoan Luu Huu
7e046ac7f3 TTS/STT languages and voices for each provider (#281)
* wip

* wip

* wip

* wip

* wip

* wip

* add testcase
2024-01-08 18:15:40 -05:00
Hoan Luu Huu
997ff05f3c support tts deepgram (#277) 2023-12-26 07:47:19 -05:00
Hoan Luu Huu
55d8fdef1c fix nuance onprem stt does not have data (#276) 2023-12-19 09:12:40 -05:00
Dave Horton
7d355f2fac enable azure stt through a proxy (#275) 2023-12-18 12:37:10 -05:00
Hoan Luu Huu
c6b8ec1b28 fix upload recording (#274) 2023-12-18 08:36:25 -05:00
Hoan Luu Huu
10159a0ba6 fix azure tts test from speech-utils (#272)
* fix azure tts test from speech-utils

* wip
2023-12-06 22:10:55 -05:00
Hoan Luu Huu
7a558c7349 update speech utils version (#270) 2023-12-05 20:48:19 -05:00
Hoan Luu Huu
4dbe7af9db fix list registered user does not have registered_status field (#268) 2023-12-05 07:44:43 -05:00
Hoan Luu Huu
4ec34faa29 support inband dtmf (#261)
* support inband dtmf

* support inband dtmf

* wip
2023-12-02 11:31:07 -05:00
Hoan Luu Huu
e2d6086f9f elevenlabs new model and options (#267) 2023-11-30 14:01:50 -05:00
Hoan Luu Huu
0e056ad296 support getting registered user details (#265)
* support getting registered user details

* add swager

* fix to use new registrar api

* assert sip_realm should be available for registered sip user query

* update mw registra version
2023-11-28 08:44:45 -05:00
Anton Voylenko
1d69457ddc support for llc tag (#262) 2023-11-17 08:48:34 -05:00
Anton Voylenko
dcfe6cc05d swagger: update create call spec (#260) 2023-11-14 10:40:31 -05:00
Hoan Luu Huu
a474c2d4cc let realtimedb-help build redis configuraiton from env variables (#256)
* let realtimedb-help build redis configuraiton from env variables

* update speech-utils version
2023-11-14 08:57:16 -05:00
Hoan Luu Huu
0f244cf6d5 fix create update lcr generate too much request (#259)
* fix create update lcr generate too much request

* wip
2023-11-14 08:07:04 -05:00
Dave Horton
92d9468570 0.8.5 2023-11-09 12:37:01 -05:00
Hoan Luu Huu
1b143f6aae support whisper tts (#255)
* support whisper tts

* wip

* update speech util version
2023-11-09 08:38:06 -05:00
Hoan Luu Huu
43344ae14b user retriction (#254)
* user retriction

* wip

* wip

* wip
2023-11-08 12:40:28 -05:00
Dave Horton
e6168d0a3c changes to enable/disable direct calling from chrome extension (#253)
* changes to enable/disable direct calling from chrome extension

* changes to db upgrade script

* fix upgrade db script

---------

Co-authored-by: Quan HL <quan.luuhoang8@gmail.com>
2023-11-06 09:37:35 -05:00
Hoan Luu Huu
f725d5f0a1 feat TTS synthAduio from API (#252)
* feat TTS synthAduio from API

* fix failing testcase

* wip

* wip

* wip

* wip

* uip

* wip

* fix

* fix

* swagger
2023-11-06 07:48:52 -05:00
Hoan Luu Huu
1e9f388f51 feat get registered client status (#251) 2023-11-03 07:33:03 -04:00
Hoan Luu Huu
72d2877ddf support assembly ai (#248)
* support assembly ai

* wip

* wip
2023-11-01 10:43:42 -04:00
Hoan Luu Huu
f15c339a2a support assembly ai (#248)
* support assembly ai

* wip

* wip
2023-11-01 08:02:32 -04:00
Hoan Luu Huu
30ba84d57b google custom voice (#245)
* google custom voice

* wip

* wip

* fix failing testcase
2023-10-30 20:29:12 -04:00
Dave Horton
a4e767e1e4 more useful logging of dns errors 2023-10-25 14:28:48 -04:00
Dave Horton
7b805130bb Feature/retrieve registered users (#243)
* add GET /Accounts/:sid/RegisteredSipUsers

* fix vulnerabilities
2023-10-22 11:32:59 -04:00
Hoan Luu Huu
a1c302f85c Fix/getvoicelanguage (#242)
* fix fetch eleven voice and language

* fix elevenlabs voice and language

* fix

* fix

* fix

* fix

* fix

* fix
2023-10-20 08:41:26 +02:00
Hoan Luu Huu
bf9ae3b5ce fix fetch eleven voice and language (#241) 2023-10-13 08:06:35 -04:00
Hoan Luu Huu
4c9af253a3 add eleven labs (#240)
* add eleven labs

* wip

* add voices and languages endpoints
2023-10-12 21:27:23 -04:00
Hoan Luu Huu
936a9244ba add sentinal Password option (#239) 2023-10-04 19:21:36 -04:00
Markus Frindt
0522ae408c Fix the query to retrieve a phone number as account user (#237)
Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
2023-09-27 10:09:54 -04:00
Hoan Luu Huu
9c788cdedc add cobalt stt (#233)
* add cobalt stt

* fix review comments
2023-09-26 09:00:16 -04:00
Hoan Luu Huu
cbc5e2d6f7 fix jambones-sql.sql missing FOREIGN_KEY_CHECKS (#236) 2023-09-25 19:57:43 -04:00
Hoan Luu Huu
f4d6fd14b8 allow sip port is null (#232)
* allow sip port is null

* update upgrade script

* fix review comment
2023-09-25 19:54:43 -04:00
Anton Voylenko
b190334839 validate recording auth (#235) 2023-09-24 08:19:13 -04:00
Hoan Luu Huu
209a58ff51 add try catch for mp3 encoder (#234) 2023-09-20 22:31:17 -04:00
105 changed files with 23662 additions and 11393 deletions

View File

@@ -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

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Drachtio Communications Services, LLC
Copyright (c) 2018-2024 FirstFive8, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -33,6 +33,8 @@ Configuration is provided via environment variables:
|K8S| service running as kubernetes service |no|
|K8S_FEATURE_SERVER_SERVICE_NAME| feature server name(required for K8S) |no|
|K8S_FEATURE_SERVER_SERVICE_PORT| feature server port(required for K8S) |no|
|JAMBONZ_RECORD_WS_USERNAME| recording websocket username|no|
|JAMBONZ_RECORD_WS_PASSWORD| recording websocket password|no|
#### Database dependency
A mysql database is used to store long-lived objects such as Accounts, Applications, etc. To create the database schema, use or review the scripts in the 'db' folder, particularly:

62
app.js
View File

@@ -7,7 +7,9 @@ const nocache = require('nocache');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const passport = require('passport');
const {verifyViewOnlyUser} = require('./lib/middleware');
const routes = require('./lib/routes');
const Registrar = require('@jambonz/mw-registrar');
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_USER &&
@@ -35,6 +37,7 @@ const {
logger, process.env.JAMBONES_TIME_SERIES_HOST
);
const {
client,
retrieveCall,
deleteCall,
listCalls,
@@ -45,16 +48,16 @@ const {
retrieveKey,
deleteKey,
incrKey,
JAMBONES_REDIS_SENTINELS
listConferences
} = require('./lib/helpers/realtimedb-helpers');
const {
getTtsVoices,
getTtsSize,
purgeTtsCache
} = require('@jambonz/speech-utils')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
purgeTtsCache,
getAwsAuthToken,
getVerbioAccessToken,
synthAudio
} = require('@jambonz/speech-utils')({}, logger);
const {
lookupAppBySid,
lookupAccountBySid,
@@ -62,7 +65,8 @@ const {
lookupAppByPhoneNumber,
lookupCarrierBySid,
lookupSipGatewayBySid,
lookupSmppGatewayBySid
lookupSmppGatewayBySid,
lookupClientByAccountAndUsername
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
@@ -81,11 +85,13 @@ passport.use(authStrategy);
app.locals = app.locals || {};
app.locals = {
...app.locals,
registrar: new Registrar(logger, client),
logger,
retrieveCall,
deleteCall,
listCalls,
listSortedSets,
listConferences,
purgeCalls,
retrieveSet,
addKey,
@@ -94,7 +100,10 @@ app.locals = {
deleteKey,
getTtsVoices,
getTtsSize,
getAwsAuthToken,
getVerbioAccessToken,
purgeTtsCache,
synthAudio,
lookupAppBySid,
lookupAccountBySid,
lookupAccountByPhoneNumber,
@@ -102,6 +111,7 @@ app.locals = {
lookupCarrierBySid,
lookupSipGatewayBySid,
lookupSmppGatewayBySid,
lookupClientByAccountAndUsername,
queryCdrs,
queryCdrsSP,
queryAlerts,
@@ -163,6 +173,19 @@ app.use('/v1', unless(
'/InviteCodes',
'/PredefinedCarriers'
], passport.authenticate('bearer', {session: false})));
app.use('/v1', unless(
[
'/register',
'/forgot-password',
'/signin',
'/login',
'/messaging',
'/outboundSMS',
'/AccountTest',
'/InviteCodes',
'/PredefinedCarriers',
'/logout'
], verifyViewOnlyUser));
app.use('/', routes);
app.use((err, req, res, next) => {
logger.error(err, 'burped error');
@@ -175,11 +198,22 @@ const server = app.listen(PORT);
const isValidWsKey = (hdr) => {
const username = process.env.JAMBONZ_RECORD_WS_USERNAME;
const password = process.env.JAMBONZ_RECORD_WS_PASSWORD;
const token = Buffer.from(`${username}:${password}`).toString('base64');
const arr = /^Basic (.*)$/.exec(hdr);
return arr[1] === token;
const username = process.env.JAMBONZ_RECORD_WS_USERNAME || process.env.JAMBONES_RECORD_WS_USERNAME;
const password = process.env.JAMBONZ_RECORD_WS_PASSWORD || process.env.JAMBONES_RECORD_WS_PASSWORD;
if (username && password) {
if (!hdr) {
// auth header is missing
return false;
}
const token = Buffer.from(`${username}:${password}`).toString('base64');
const arr = /^Basic (.*)$/.exec(hdr);
if (!Array.isArray(arr)) {
// malformed auth header
return false;
}
return arr[1] === token;
}
return true;
};
server.on('upgrade', (request, socket, head) => {
@@ -196,13 +230,13 @@ server.on('upgrade', (request, socket, head) => {
/* verify the api key */
if (!isValidWsKey(request.headers['authorization'])) {
logger.info(`invalid auth header: ${request.headers['authorization']}`);
logger.info(`invalid auth header: ${request.headers['authorization'] || 'authorization header missing'}`);
return socket.write('HTTP/1.1 403 Forbidden \r\n\r\n', () => socket.destroy());
}
/* 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);
});
});

22
db/export_jambonz.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# This script exports the 'jambones' database (schema and data)
# from the source MySQL server into a file.
# Configuration variables
SOURCE_HOST=
DB_USER=
DB_PASS=
DB_NAME=
EXPORT_FILE="jambones_export.sql"
# Export the database using mysqldump
echo "Exporting database '$DB_NAME' from $SOURCE_HOST..."
mysqldump -h "$SOURCE_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$EXPORT_FILE"
# Check for errors
if [ $? -eq 0 ]; then
echo "Database export successful. Export file created: $EXPORT_FILE"
else
echo "Error exporting database '$DB_NAME'."
exit 1
fi

31
db/import_jambonz.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# This script imports the SQL dump file into the target MySQL server.
# It first drops the existing 'jambones' database (if it exists),
# recreates it, and then imports the dump file.
# Configuration variables
TARGET_HOST=
DB_USER=
DB_PASS=
DB_NAME=
IMPORT_FILE="jambones_export.sql"
# Drop the existing database (if any) and create a new one
echo "Dropping and recreating database '$DB_NAME' on $TARGET_HOST..."
mysql -h "$TARGET_HOST" -u "$DB_USER" -p"$DB_PASS" -e "DROP DATABASE IF EXISTS \`$DB_NAME\`; CREATE DATABASE \`$DB_NAME\`;"
if [ $? -ne 0 ]; then
echo "Error dropping/creating database '$DB_NAME'."
exit 1
fi
# Import the SQL dump into the newly created database
echo "Importing dump file '$IMPORT_FILE' into database '$DB_NAME'..."
mysql -h "$TARGET_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < "$IMPORT_FILE"
if [ $? -eq 0 ]; then
echo "Database import successful."
else
echo "Error importing the database."
exit 1
fi

View File

@@ -1,4 +1,5 @@
/* SQLEditor (MySQL (2))*/
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS account_static_ips;
@@ -53,6 +54,8 @@ DROP TABLE IF EXISTS signup_history;
DROP TABLE IF EXISTS smpp_addresses;
DROP TABLE IF EXISTS google_custom_voices;
DROP TABLE IF EXISTS speech_credentials;
DROP TABLE IF EXISTS system_information;
@@ -136,6 +139,9 @@ account_sid CHAR(36) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT 1,
username VARCHAR(64),
password VARCHAR(1024),
allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1,
allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1,
allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1,
PRIMARY KEY (client_sid)
);
@@ -156,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 pat';
) COMMENT='An ordered list of digit patterns in an LCR table. The patterns are tested in sequence until one matches';
CREATE TABLE lcr
(
@@ -167,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 ';
) 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.';
CREATE TABLE password_settings
(
@@ -338,11 +344,25 @@ label VARCHAR(64),
PRIMARY KEY (speech_credential_sid)
);
CREATE TABLE google_custom_voices
(
google_custom_voice_sid CHAR(36) NOT NULL UNIQUE ,
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)
);
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
@@ -396,6 +416,8 @@ 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',
outbound_sip_proxy VARCHAR(255),
PRIMARY KEY (voip_carrier_sid)
) COMMENT='A Carrier or customer PBX that can send or receive calls';
@@ -437,11 +459,13 @@ CREATE TABLE sip_gateways
sip_gateway_sid CHAR(36),
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
netmask INTEGER NOT NULL DEFAULT 32,
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
port INTEGER COMMENT 'sip signaling port',
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
voip_carrier_sid CHAR(36) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT 1,
send_options_ping BOOLEAN NOT NULL DEFAULT 0,
use_sips_scheme BOOLEAN NOT NULL DEFAULT 0,
pad_crypto BOOLEAN NOT NULL DEFAULT 0,
protocol ENUM('udp','tcp','tls', 'tls/srtp') DEFAULT 'udp' COMMENT 'Outbound call protocol',
PRIMARY KEY (sip_gateway_sid)
@@ -479,7 +503,7 @@ messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
app_json TEXT,
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
speech_synthesis_voice VARCHAR(64),
speech_synthesis_voice VARCHAR(256),
speech_synthesis_label VARCHAR(64),
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
@@ -487,7 +511,7 @@ speech_recognizer_label VARCHAR(64),
use_for_fallback_speech BOOLEAN DEFAULT false,
fallback_speech_synthesis_vendor VARCHAR(64),
fallback_speech_synthesis_language VARCHAR(12),
fallback_speech_synthesis_voice VARCHAR(64),
fallback_speech_synthesis_voice VARCHAR(256),
fallback_speech_synthesis_label VARCHAR(64),
fallback_speech_recognizer_vendor VARCHAR(64),
fallback_speech_recognizer_language VARCHAR(64),
@@ -534,6 +558,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';
@@ -628,6 +653,10 @@ ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (ser
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX google_custom_voice_sid_idx ON google_custom_voices (google_custom_voice_sid);
CREATE INDEX speech_credential_sid_idx ON google_custom_voices (speech_credential_sid);
ALTER TABLE google_custom_voices ADD FOREIGN KEY speech_credential_sid_idxfk (speech_credential_sid) REFERENCES speech_credentials (speech_credential_sid) ON DELETE CASCADE;
CREATE INDEX user_sid_idx ON users (user_sid);
CREATE INDEX email_idx ON users (email);
CREATE INDEX phone_idx ON users (phone);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
/* remove VIEW_ONLY permission for admin user as it will prevent write operations*/
delete from user_permissions;
delete from permissions;
insert into permissions (permission_sid, name, description)
values
('ffbc342a-546a-11ed-bdc3-0242ac120002', 'VIEW_ONLY', 'Can view data but not make changes'),
('ffbc3a10-546a-11ed-bdc3-0242ac120002', 'PROVISION_SERVICES', 'Can provision services'),
('ffbc3c5e-546a-11ed-bdc3-0242ac120002', 'PROVISION_USERS', 'Can provision users');

View File

@@ -6,6 +6,7 @@ const {readFile} = require('fs/promises');
const {execSync} = require('child_process');
const {version:desiredVersion} = require('../package.json');
const logger = require('pino')();
const fs = require('fs');
logger.info(`upgrade-jambonz-db: desired version ${desiredVersion}`);
@@ -22,6 +23,20 @@ const opts = {
port: process.env.JAMBONES_MYSQL_PORT || 3306,
multipleStatements: true
};
const rejectUnauthorized = process.env.JAMBONES_MYSQL_REJECT_UNAUTHORIZED;
const ssl_ca_file = process.env.JAMBONES_MYSQL_SSL_CA_FILE;
const ssl_cert_file = process.env.JAMBONES_MYSQL_SSL_CERT_FILE;
const ssl_key_file = process.env.JAMBONES_MYSQL_SSL_KEY_FILE;
const sslFilesProvided = Boolean(ssl_ca_file && ssl_cert_file && ssl_key_file);
if (rejectUnauthorized !== undefined || sslFilesProvided) {
opts.ssl = {
...(rejectUnauthorized !== undefined && { rejectUnauthorized: rejectUnauthorized === '0' ? false : true }),
...(ssl_ca_file && { ca: fs.readFileSync(ssl_ca_file) }),
...(ssl_cert_file && { cert: fs.readFileSync(ssl_cert_file) }),
...(ssl_key_file && { key: fs.readFileSync(ssl_key_file) })
};
}
const sql = {
'7006': [
@@ -88,7 +103,7 @@ const sql = {
'ALTER TABLE user_permissions ADD FOREIGN KEY permission_sid_idxfk (permission_sid) REFERENCES permissions (permission_sid)',
'ALTER TABLE `users` ADD COLUMN `is_active` BOOLEAN NOT NULL default true',
],
8003: [
'8003': [
'SET FOREIGN_KEY_CHECKS=0',
'ALTER TABLE `voip_carriers` ADD COLUMN `register_status` VARCHAR(4096)',
'ALTER TABLE `sbc_addresses` ADD COLUMN `last_updated` DATETIME',
@@ -140,7 +155,7 @@ const sql = {
'ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_3 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid)',
'SET FOREIGN_KEY_CHECKS=1',
],
8004: [
'8004': [
'alter table accounts add column record_all_calls BOOLEAN NOT NULL DEFAULT false',
'alter table accounts add column bucket_credential VARCHAR(8192)',
'alter table accounts add column record_format VARCHAR(16) NOT NULL DEFAULT \'mp3\'',
@@ -160,7 +175,7 @@ const sql = {
'ALTER TABLE clients ADD CONSTRAINT account_sid_idxfk_13 FOREIGN KEY account_sid_idxfk_13 (account_sid) REFERENCES accounts (account_sid)',
'ALTER TABLE sip_gateways ADD COLUMN protocol ENUM(\'udp\',\'tcp\',\'tls\', \'tls/srtp\') DEFAULT \'udp\''
],
8005: [
'8005': [
'DROP INDEX speech_credentials_idx_1 ON speech_credentials',
'ALTER TABLE speech_credentials ADD COLUMN label VARCHAR(64)',
'ALTER TABLE applications ADD COLUMN speech_synthesis_label VARCHAR(64)',
@@ -173,7 +188,43 @@ const sql = {
'ALTER TABLE applications ADD COLUMN fallback_speech_recognizer_vendor VARCHAR(64)',
'ALTER TABLE applications ADD COLUMN fallback_speech_recognizer_language VARCHAR(64)',
'ALTER TABLE applications ADD COLUMN fallback_speech_recognizer_label VARCHAR(64)',
'ALTER TABLE sip_gateways ADD COLUMN pad_crypto BOOLEAN NOT NULL DEFAULT 0,',
'ALTER TABLE sip_gateways ADD COLUMN pad_crypto BOOLEAN NOT NULL DEFAULT 0',
'ALTER TABLE sip_gateways MODIFY port INTEGER',
`CREATE TABLE google_custom_voices
(
google_custom_voice_sid CHAR(36) NOT NULL UNIQUE ,
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,
PRIMARY KEY (google_custom_voice_sid)
)
`,
'CREATE INDEX google_custom_voice_sid_idx ON google_custom_voices (google_custom_voice_sid)',
'CREATE INDEX speech_credential_sid_idx ON google_custom_voices (speech_credential_sid)',
'ALTER TABLE google_custom_voices ADD FOREIGN KEY speech_credential_sid_idxfk (speech_credential_sid) REFERENCES speech_credentials (speech_credential_sid) ON DELETE CASCADE',
'ALTER TABLE clients ADD COLUMN allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1',
'ALTER TABLE clients ADD COLUMN allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1',
'ALTER TABLE clients ADD COLUMN allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1',
],
9000: [
'ALTER TABLE sip_gateways ADD COLUMN send_options_ping BOOLEAN NOT NULL DEFAULT 0',
'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\'',
'ALTER TABLE voip_carriers ADD COLUMN outbound_sip_proxy VARCHAR(255)',
]
};
@@ -206,6 +257,9 @@ const doIt = async() => {
if (val < 8003) upgrades.push(...sql['8003']);
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..');

View File

@@ -35,8 +35,8 @@ function makeStrategy(logger) {
debug(err);
logger.info({err}, 'Error checking redis for jwt');
}
const { user_sid, service_provider_sid, account_sid, email, name, scope, permissions } = decoded;
const { user_sid, service_provider_sid, account_sid, email,
name, scope, permissions, is_view_only } = decoded;
const user = {
service_provider_sid,
account_sid,
@@ -45,6 +45,7 @@ function makeStrategy(logger) {
email,
name,
permissions,
is_view_only,
hasScope: (s) => s === scope,
hasAdminAuth: scope === 'admin',
hasServiceProviderAuth: scope === 'service_provider',

View File

@@ -1,27 +1,7 @@
const logger = require('../logger');
const JAMBONES_REDIS_SENTINELS = process.env.JAMBONES_REDIS_SENTINELS ? {
sentinels: process.env.JAMBONES_REDIS_SENTINELS.split(',').map((sentinel) => {
let host, port = 26379;
if (sentinel.includes(':')) {
const arr = sentinel.split(':');
host = arr[0];
port = parseInt(arr[1], 10);
} else {
host = sentinel;
}
return {host, port};
}),
name: process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
...(process.env.JAMBONES_REDIS_SENTINEL_PASSWORD && {
password: process.env.JAMBONES_REDIS_SENTINEL_PASSWORD
}),
...(process.env.JAMBONES_REDIS_SENTINEL_USERNAME && {
username: process.env.JAMBONES_REDIS_SENTINEL_USERNAME
})
} : null;
const {
client,
retrieveCall,
deleteCall,
listCalls,
@@ -33,12 +13,11 @@ const {
deleteKey,
incrKey,
client: redisClient,
} = require('@jambonz/realtimedb-helpers')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
listConferences
} = require('@jambonz/realtimedb-helpers')({}, logger);
module.exports = {
client,
retrieveCall,
deleteCall,
listCalls,
@@ -50,5 +29,5 @@ module.exports = {
deleteKey,
redisClient,
incrKey,
JAMBONES_REDIS_SENTINELS
listConferences
};

View File

@@ -1,4 +1,5 @@
const logger = require('./logger');
const {UserPermissionError} = require('./utils/errors');
function delayLoginMiddleware(req, res, next) {
if (req.path.includes('/login') || req.path.includes('/signin')) {
@@ -27,6 +28,26 @@ function delayLoginMiddleware(req, res, next) {
next();
}
function verifyViewOnlyUser(req, res, next) {
// Skip check for GET requests
if (req.method === 'GET') {
return next();
}
// current user is changing their password which shuould be allowed
if (req.body?.old_password && req.body?.new_password) {
return next();
}
// Check if user is read-only
if (req.user && !!req.user.is_view_only) {
const upError = new UserPermissionError('User has view-only access');
upError.status = 403;
throw upError;
}
next();
}
module.exports = {
delayLoginMiddleware
delayLoginMiddleware,
verifyViewOnlyUser
};

View File

@@ -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);
}
};
@@ -199,8 +203,9 @@ class Account extends Model {
debug(r3, 'Account.activateSubscription - replaced old subscription');
/* update account.plan to paid, if it isnt already */
/* update account.is_active to 1, if account is deactivated */
await promisePool.execute(
'UPDATE accounts SET plan_type = \'paid\' WHERE account_sid = ?',
'UPDATE accounts SET plan_type = \'paid\', is_active = 1 WHERE account_sid = ?',
[account_sid]);
return true;
}

View File

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

View File

@@ -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;
@@ -52,6 +52,18 @@ Client.fields = [
{
name: 'password',
type: 'string'
},
{
name: 'allow_direct_app_calling',
type: 'number'
},
{
name: 'allow_direct_queue_calling',
type: 'number'
},
{
name: 'allow_direct_user_calling',
type: 'number'
}
];

View File

@@ -0,0 +1,61 @@
const Model = require('./model');
const {promisePool} = require('../db');
class GoogleCustomVoice extends Model {
constructor() {
super();
}
static async retrieveAllBySpeechCredentialSid(speech_credential_sid) {
const sql = `SELECT * FROM ${this.table} WHERE speech_credential_sid = ?`;
const [rows] = await promisePool.query(sql, speech_credential_sid);
return rows;
}
static async deleteAllBySpeechCredentialSid(speech_credential_sid) {
const sql = `DELETE FROM ${this.table} WHERE speech_credential_sid = ?`;
const [rows] = await promisePool.query(sql, speech_credential_sid);
return rows;
}
static async retrieveAllByLabel(service_provider_sid, account_sid, label) {
let sql;
if (account_sid) {
sql = `SELECT gcv.* FROM ${this.table} gcv
LEFT JOIN speech_credentials sc ON gcv.speech_credential_sid = sc.speech_credential_sid
WHERE sc.account_sid = ? OR (sc.account_sid is NULL && sc.service_provider_sid = ?)
${label ? 'AND label = ?' : 'AND label is NULL'}`;
} else {
sql = `SELECT gcv.* FROM ${this.table} gcv
LEFT JOIN speech_credentials sc ON gcv.speech_credential_sid = sc.speech_credential_sid
WHERE sc.service_provider_sid = ? ${label ? 'AND label = ?' : 'AND label is NULL'}`;
}
const [rows] = await promisePool.query(sql, [...(account_sid ?
[account_sid, service_provider_sid] : [service_provider_sid]), label]);
return rows;
}
}
GoogleCustomVoice.table = 'google_custom_voices';
GoogleCustomVoice.fields = [
{
name: 'google_custom_voice_sid',
type: 'string',
primaryKey: true
},
{
name: 'model',
type: 'string',
required: true
},
{
name: 'reported_usage',
type: 'number'
},
{
name: 'name',
type: 'string',
required: true
}
];
module.exports = GoogleCustomVoice;

45
lib/models/permissions.js Normal file
View File

@@ -0,0 +1,45 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sqlAll = `
SELECT * from permissions
`;
const sqlByName = `
SELECT * from permissions where name = ?
`;
class Permissions extends Model {
constructor() {
super();
}
static async retrieveAll() {
const [rows] = await promisePool.query(sqlAll);
return rows;
}
static async retrieveByName(name) {
const [rows] = await promisePool.query(sqlByName, [name]);
return rows;
}
}
Permissions.table = 'permissions';
Permissions.fields = [
{
name: 'permission_sid',
type: 'string',
primaryKey: true
},
{
name: 'name',
type: 'string',
required: true
},
{
name: 'description',
type: 'string',
required: true
}
];
module.exports = Permissions;

View File

@@ -1,6 +1,7 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sql = 'SELECT * from phone_numbers WHERE account_sid = ? ORDER BY number';
const sqlRetrieveAll = 'SELECT * from phone_numbers WHERE account_sid = ? ORDER BY number';
const sqlRetrieveOne = 'SELECT * from phone_numbers WHERE phone_number_sid = ? AND account_sid = ? ORDER BY number';
const sqlSP = `SELECT *
FROM phone_numbers
WHERE account_sid IN
@@ -17,7 +18,7 @@ class PhoneNumber extends Model {
static async retrieveAll(account_sid) {
if (!account_sid) return await super.retrieveAll();
const [rows] = await promisePool.query(sql, account_sid);
const [rows] = await promisePool.query(sqlRetrieveAll, account_sid);
return rows;
}
static async retrieveAllForSP(service_provider_sid) {
@@ -30,7 +31,7 @@ class PhoneNumber extends Model {
*/
static async retrieve(sid, account_sid) {
if (!account_sid) return super.retrieve(sid);
const [rows] = await promisePool.query(`${sql} AND phone_number_sid = ?`, [account_sid, sid]);
const [rows] = await promisePool.query(sqlRetrieveOne, [sid, account_sid]);
return rows;
}
}

View File

@@ -20,14 +20,19 @@ class SpeechCredential extends Model {
return rows;
}
static async isAvailableVendorAndLabel(service_provider_sid, account_sid, vendor, label) {
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 = ? AND label = ?';
} else {
sql = 'SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ? 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;
}

View File

@@ -33,6 +33,10 @@ SystemInformation.fields = [
name: 'monitoring_domain_name',
type: 'string',
},
{
name: 'private_network_cidr',
type: 'string',
},
];
module.exports = SystemInformation;

View File

@@ -0,0 +1,53 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sqlAll = `
SELECT * from user_permissions
`;
const sqlByUserIdPermissionSid = `
SELECT * from user_permissions where user_sid = ? and permission_sid = ?
`;
const sqlByUserId = `
SELECT * from user_permissions where user_sid = ?
`;
class UserPermissions extends Model {
constructor() {
super();
}
static async retrieveAll() {
const [rows] = await promisePool.query(sqlAll);
return rows;
}
static async retrieveByUserIdPermissionSid(user_sid, permission_sid) {
const [rows] = await promisePool.query(sqlByUserIdPermissionSid, [user_sid, permission_sid]);
return rows;
}
static async retrieveByUserId(user_sid) {
const [rows] = await promisePool.query(sqlByUserId, [user_sid]);
return rows;
}
}
UserPermissions.table = 'user_permissions';
UserPermissions.fields = [
{
name: 'user_permissions_sid',
type: 'string',
primaryKey: true
},
{
name: 'user_sid',
type: 'string',
required: true
},
{
name: 'permission_sid',
type: 'string',
required: true
}
];
module.exports = UserPermissions;

View File

@@ -61,6 +61,10 @@ VoipCarrier.fields = [
name: 'requires_register',
type: 'number'
},
{
name: 'register_use_tls',
type: 'number'
},
{
name: 'register_username',
type: 'string'
@@ -132,6 +136,14 @@ VoipCarrier.fields = [
{
name: 'register_status',
type: 'string'
},
{
name: 'dtmf_type',
type: 'string'
},
{
name: 'sip_proxy',
type: 'string'
}
];

View File

@@ -1,28 +1,58 @@
const { Writable } = require('stream');
const { BlobServiceClient } = require('@azure/storage-blob');
const { v4: uuidv4 } = require('uuid');
const streamBuffers = require('stream-buffers');
class AzureStorageUploadStream extends Writable {
constructor(logger, opts) {
super(opts);
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
this.blockBlobClient = blobServiceClient.getContainerClient(opts.bucketName).getBlockBlobClient(opts.Key);
this.metadata = opts.metadata;
this.blocks = [];
this.bufferSize = 2 * 1024 * 1024; // Buffer size set to 2MB
this.buffer = new streamBuffers.WritableStreamBuffer({
initialSize: this.bufferSize,
incrementAmount: this.bufferSize
});
}
async _write(chunk, encoding, callback) {
const blockID = uuidv4().replace(/-/g, '');
this.blocks.push(blockID);
try {
await this.blockBlobClient.stageBlock(blockID, chunk, chunk.length);
this.buffer.write(chunk, encoding);
if (this.buffer.size() >= this.bufferSize) {
const blockID = uuidv4().replace(/-/g, '');
this.blocks.push(blockID);
try {
const dataToWrite = this.buffer.getContents();
await this.blockBlobClient.stageBlock(blockID, dataToWrite, dataToWrite.length);
callback();
} catch (error) {
callback(error);
}
} else {
callback();
} catch (error) {
callback(error);
}
}
async _final(callback) {
// Write any remaining data in buffer
if (this.buffer.size() > 0) {
const remainingData = this.buffer.getContents();
const blockID = uuidv4().replace(/-/g, '');
this.blocks.push(blockID);
try {
await this.blockBlobClient.stageBlock(blockID, remainingData, remainingData.length);
} catch (error) {
callback(error);
return;
}
}
try {
await this.blockBlobClient.commitBlockList(this.blocks);
// remove all null/undefined props

View File

@@ -2,7 +2,7 @@ const { Transform } = require('stream');
const lamejs = require('@jambonz/lamejs');
class PCMToMP3Encoder extends Transform {
constructor(options) {
constructor(options, logger) {
super(options);
const channels = options.channels || 1;
@@ -11,33 +11,40 @@ class PCMToMP3Encoder extends Transform {
this.encoder = new lamejs.Mp3Encoder(channels, sampleRate, bitRate);
this.channels = channels;
this.logger = logger;
}
_transform(chunk, encoding, callback) {
// Convert chunk buffer into Int16Array for lamejs
const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / 2);
try {
// Convert chunk buffer into Int16Array for lamejs
const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / 2);
// Split input samples into left and right channel arrays if stereo
let leftChannel, rightChannel;
if (this.channels === 2) {
leftChannel = new Int16Array(samples.length / 2);
rightChannel = new Int16Array(samples.length / 2);
// Split input samples into left and right channel arrays if stereo
let leftChannel, rightChannel;
if (this.channels === 2) {
leftChannel = new Int16Array(samples.length / 2);
rightChannel = new Int16Array(samples.length / 2);
for (let i = 0; i < samples.length; i += 2) {
leftChannel[i / 2] = samples[i];
rightChannel[i / 2] = samples[i + 1];
for (let i = 0; i < samples.length; i += 2) {
leftChannel[i / 2] = samples[i];
rightChannel[i / 2] = samples[i + 1];
}
} else {
leftChannel = samples;
}
} else {
leftChannel = samples;
}
// Encode the input data
const mp3Data = this.encoder.encodeBuffer(leftChannel, rightChannel);
// Encode the input data
const mp3Data = this.encoder.encodeBuffer(leftChannel, rightChannel);
if (mp3Data.length > 0) {
this.push(Buffer.from(mp3Data));
if (mp3Data.length > 0) {
this.push(Buffer.from(mp3Data));
}
callback();
} catch (err) {
this.logger.error(
{ err },
'Error while mp3 transform');
}
callback();
}
_flush(callback) {

View File

@@ -1,5 +1,6 @@
const { Storage } = require('@google-cloud/storage');
const { Writable } = require('stream');
const streamBuffers = require('stream-buffers');
class GoogleStorageUploadStream extends Writable {
@@ -12,18 +13,38 @@ class GoogleStorageUploadStream extends Writable {
this.gcsFile = storage.bucket(opts.bucketName).file(opts.Key);
this.writeStream = this.gcsFile.createWriteStream();
this.bufferSize = 2 * 1024 * 1024; // Buffer size set to 2MB
this.buffer = new streamBuffers.WritableStreamBuffer({
initialSize: this.bufferSize,
incrementAmount: this.bufferSize
});
this.writeStream.on('error', (err) => this.logger.error(err));
this.writeStream.on('finish', () => {
this.logger.info('google storage Upload completed.');
this.logger.info('Google storage Upload completed.');
this._addMetadata();
});
}
_write(chunk, encoding, callback) {
this.writeStream.write(chunk, encoding, callback);
this.buffer.write(chunk, encoding);
// Write to GCS when buffer reaches desired size
if (this.buffer.size() >= this.bufferSize) {
const dataToWrite = this.buffer.getContents();
this.writeStream.write(dataToWrite, callback);
} else {
callback();
}
}
_final(callback) {
// Write any remaining data in the buffer to GCS
if (this.buffer.size() > 0) {
const remainingData = this.buffer.getContents();
this.writeStream.write(remainingData);
}
this.writeStream.end();
this.writeStream.once('finish', callback);
}
@@ -33,7 +54,7 @@ class GoogleStorageUploadStream extends Writable {
await this.gcsFile.setMetadata({metadata: this.metadata});
this.logger.info('Google storage Upload and metadata setting completed.');
} catch (err) {
this.logger.error(err, 'Google storage An error occurred while setting metadata');
this.logger.error(err, 'Google storage An error occurred while setting metadata');
}
}
}

View File

@@ -11,7 +11,7 @@ class S3MultipartUploadStream extends Writable {
super(opts);
this.logger = logger;
this.bucketName = opts.bucketName;
this.objectKey = opts.objectKey;
this.objectKey = opts.Key;
this.uploadId = null;
this.partNumber = 1;
this.multipartETags = [];

View File

@@ -3,6 +3,7 @@ const Websocket = require('ws');
const PCMToMP3Encoder = require('./encoder');
const wav = require('wav');
const { getUploader } = require('./utils');
const { pipeline } = require('stream');
async function upload(logger, socket) {
socket._recvInitialMetadata = false;
@@ -58,24 +59,21 @@ async function upload(logger, socket) {
channels: 2,
sampleRate: sampleRate,
bitrate: 128
});
}, logger);
}
const handleError = (err, streamType) => {
logger.error(
{ err },
`Error while streaming for vendor: ${obj.vendor}, pipe: ${streamType}: ${err.message}`
);
};
/* start streaming data */
const duplex = Websocket.createWebSocketStream(socket);
duplex
.on('error', (err) => handleError(err, 'duplex'))
.pipe(encoder)
.on('error', (err) => handleError(err, 'encoder'))
.pipe(uploadStream)
.on('error', (err) => handleError(err, 'uploadStream'));
pipeline(
Websocket.createWebSocketStream(socket),
encoder,
uploadStream,
(error) => {
if (error) {
logger.error({ error }, 'pipeline error, cannot upload data to storage');
socket.close();
}
}
);
} else {
logger.info(`account ${accountSid} does not have any bucket credential, close the socket`);
socket.close();

View File

@@ -5,7 +5,7 @@ const S3MultipartUploadStream = require('./s3-multipart-upload-stream');
const getUploader = (key, metadata, bucket_credential, logger) => {
const uploaderOpts = {
bucketName: bucket_credential.name,
objectKey: key,
Key: key,
metadata
};
try {

View File

@@ -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');
@@ -146,6 +147,88 @@ router.post('/:sid/VoipCarriers', async(req, res) => {
}
});
router.get('/:sid/RegisteredSipUsers', async(req, res) => {
const {logger, registrar} = req.app.locals;
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
const result = await Account.retrieve(account_sid);
if (!result || result.length === 0) {
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
}
if (!result[0].sip_realm) {
throw new DbErrorBadRequest('account does not have sip_realm configuration');
}
const users = await registrar.getRegisteredUsersForRealm(result[0].sip_realm);
res.status(200).json(users.map((u) => `${u}@${result[0].sip_realm}`));
} catch (err) {
sysError(logger, res, err);
}
});
router.post('/:sid/RegisteredSipUsers', async(req, res) => {
const {logger, registrar} = req.app.locals;
const users = req.body;
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
const result = await Account.retrieve(account_sid);
if (!result || result.length === 0) {
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
}
if (!result[0].sip_realm) {
throw new DbErrorBadRequest('account does not have sip_realm configuration');
}
if (!users || !Array.isArray(users) || users.length === 0) {
return res.status(200).json(await registrar.getRegisteredUsersDetailsForRealm(result[0].sip_realm));
}
const ret = [];
for (const u of users) {
const user = await registrar.query(`${u}@${result[0].sip_realm}`) || {
name: u,
contact: null,
expiryTime: 0,
protocol: null
};
ret.push({
name: u,
...user,
registered_status: user.expiryTime > 0 ? 'active' : 'inactive',
});
}
res.status(200).json(ret);
} catch (err) {
sysError(logger, res, err);
}
});
router.get('/:sid/RegisteredSipUsers/:client', async(req, res) => {
const {logger, registrar, lookupClientByAccountAndUsername} = req.app.locals;
const client = req.params.client;
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
const result = await Account.retrieve(account_sid);
if (!result || result.length === 0) {
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
}
const user = await registrar.query(`${client}@${result[0].sip_realm}`);
const [clientDb] = await lookupClientByAccountAndUsername(account_sid, client);
res.status(200).json({
name: client,
contact: user ? user.contact : null,
expiryTime: user ? user.expiryTime : 0,
protocol: user ? user.protocol : null,
allow_direct_app_calling: clientDb ? clientDb.allow_direct_app_calling : 0,
allow_direct_queue_calling: clientDb ? clientDb.allow_direct_queue_calling : 0,
allow_direct_user_calling: clientDb ? clientDb.allow_direct_user_calling : 0,
registered_status: user ? 'active' : 'inactive'
});
} catch (err) {
sysError(logger, res, err);
}
});
function coerceNumbers(callInfo) {
if (Array.isArray(callInfo)) {
return callInfo.map((ci) => {
@@ -181,7 +264,11 @@ function validateUpdateCall(opts) {
'conf_mute_status',
'mute_status',
'sip_request',
'record'
'record',
'tag',
'dtmf',
'conferenceParticipantAction',
'dub'
]
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
@@ -223,9 +310,28 @@ function validateUpdateCall(opts) {
if (opts.record && !opts.record.action) {
throw new DbErrorBadRequest('record requires action property');
}
if (opts.dtmf && !opts.dtmf.digit) {
throw new DbErrorBadRequest('invalid dtmf');
}
if ('startCallRecording' === opts.record?.action && !opts.record.siprecServerURL) {
throw new DbErrorBadRequest('record requires siprecServerURL property when starting recording');
}
if (opts.tag && (typeof opts.tag !== 'object' || Array.isArray(opts.tag) || opts.tag === null)) {
throw new DbErrorBadRequest('invalid tag data');
}
if (opts.conferenceParticipantAction) {
if (!['tag', 'untag', 'coach', 'uncoach', 'mute', 'unmute', 'hold', 'unhold']
.includes(opts.conferenceParticipantAction.action)) {
throw new DbErrorBadRequest(
`conferenceParticipantAction invalid action property ${opts.conferenceParticipantAction.action}`);
}
if ('tag' == opts.conferenceParticipantAction.action && !opts.conferenceParticipantAction.tag) {
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'tag\'');
}
if ('coach' == opts.conferenceParticipantAction.action && !opts.conferenceParticipantAction.tag) {
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'coach\'');
}
}
}
function validateTo(to) {
@@ -450,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);
@@ -534,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':
@@ -554,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);
@@ -563,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;
@@ -632,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) {
@@ -732,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'
@@ -999,4 +1139,21 @@ router.get('/:sid/Queues', async(req, res) => {
}
});
/**
* retrieve info for a list of conferences under an account
*/
router.get('/:sid/Conferences', async(req, res) => {
const {logger, listConferences} = req.app.locals;
try {
const accountSid = parseAccountSid(req);
await validateRequest(req, accountSid);
const conferences = await listConferences(accountSid);
logger.debug(`retrieved ${conferences.length} queues for account sid ${accountSid}`);
res.status(200).json(conferences.map((c) => c.split(':').pop()));
updateLastUsed(logger, accountSid, req).catch((err) => {});
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -16,7 +16,7 @@ router.get('/', async(req, res) => {
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
const {page, count, alert_type, days, start, end} = req.query || {};
if (!page || page < 1) throw new DbErrorBadRequest('missing or invalid "page" query arg');
if (!count || count < 25 || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
if (!count || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
if (account_sid) {
const data = await queryAlerts({

View File

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

View File

@@ -0,0 +1,137 @@
const router = require('express').Router();
const GoogleCustomVoice = require('../../models/google-custom-voice');
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);
if (!credential || credential.length === 0) {
throw new DbErrorBadRequest('Invalid speech_credential_sid');
}
const cred = credential[0];
if (req.user.hasServiceProviderAuth && cred.service_provider_sid !== req.user.service_provider_sid) {
throw new DbErrorForbidden('Insufficient privileges');
}
if (req.user.hasAccountAuth && cred.account_sid !== req.user.account_sid) {
throw new DbErrorForbidden('Insufficient privileges');
}
};
const validateAdd = async(req) => {
if (!req.body.speech_credential_sid) {
throw new DbErrorBadRequest('missing speech_credential_sid');
}
await validateCredentialPermission(req);
};
const validateUpdate = async(req) => {
if (req.body.speech_credential_sid) {
await validateCredentialPermission(req);
}
};
const preconditions = {
add: validateAdd,
update: validateUpdate,
};
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;
const service_provider_sid = req.user.service_provider_sid || req.query.service_provider_sid;
const speech_credential_sid = req.query.speech_credential_sid;
const label = req.query.label;
try {
let results = [];
if (speech_credential_sid) {
const [cred] = await SpeechCredential.retrieve(speech_credential_sid);
if (!cred) {
return res.sendStatus(404);
}
if (account_sid && cred.account_sid && cred.account_sid !== account_sid) {
throw new DbErrorForbidden('Insufficient privileges');
}
if (service_provider_sid && cred.service_provider_sid && cred.service_provider_sid !== service_provider_sid) {
throw new DbErrorForbidden('Insufficient privileges');
}
results = await GoogleCustomVoice.retrieveAllBySpeechCredentialSid(speech_credential_sid);
} else {
if (!account_sid && !service_provider_sid) {
throw new DbErrorBadRequest('missing account_sid or service_provider_sid in query parameters');
}
results = await GoogleCustomVoice.retrieveAllByLabel(service_provider_sid, account_sid, label);
}
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);
}
});
module.exports = router;

View File

@@ -52,6 +52,8 @@ api.use('/Lcrs', require('./lcrs'));
api.use('/LcrRoutes', require('./lcr-routes'));
api.use('/LcrCarrierSetEntries', require('./lcr-carrier-set-entries'));
api.use('/Clients', require('./clients'));
// Google Custom Voices
api.use('/GoogleCustomVoices', require('./google-custom-voices'));
// messaging
api.use('/Smpps', require('./smpps')); // our smpp server info

View File

@@ -100,6 +100,93 @@ const preconditions = {
decorate(router, Lcr, ['add', 'update', 'delete'], preconditions);
const validateLcrBatchAdd = async(lcr_sid, body, lookupCarrierBySid) => {
for (const lcr_route of body) {
lcr_route.lcr_sid = lcr_sid;
if (!lcr_route.lcr_carrier_set_entries || lcr_route.lcr_carrier_set_entries.length === 0) {
throw new DbErrorBadRequest('Lcr Route batch process require lcr_carrier_set_entries');
}
for (const entry of lcr_route.lcr_carrier_set_entries) {
// check voip_carrier_sid is exist
if (!entry.voip_carrier_sid) {
throw new DbErrorBadRequest('One of lcr_carrier_set_entries is missing voip_carrier_sid');
}
const carrier = await lookupCarrierBySid(entry.voip_carrier_sid);
if (!carrier) {
throw new DbErrorBadRequest('unknown voip_carrier_sid');
}
}
}
};
const addNewLcrRoute = async(lcr_route) => {
const lcr_sid = lcr_route.lcr_sid;
const lcr_carrier_set_entries = lcr_route.lcr_carrier_set_entries;
delete lcr_route.lcr_carrier_set_entries;
const lcr_route_sid = await LcrRoutes.make(lcr_route);
for (const entry of lcr_carrier_set_entries) {
entry.lcr_route_sid = lcr_route_sid;
const lcr_carrier_set_entry_sid = await LcrCarrierSetEntry.make(entry);
if (lcr_route.priority === 9999) {
// this is default lcr set entry
const [lcr] = await Lcr.retrieve(lcr_sid);
if (lcr) {
lcr.default_carrier_set_entry_sid = lcr_carrier_set_entry_sid;
delete lcr.lcr_sid;
await Lcr.update(lcr_sid, lcr);
}
}
}
};
router.post('/:sid/Routes', async(req, res) => {
const results = await Lcr.retrieve(req.params.sid);
if (results.length === 0) return res.sendStatus(404);
const {logger, lookupCarrierBySid} = req.app.locals;
try {
const body = req.body;
await validateLcrBatchAdd(req.params.sid, body, lookupCarrierBySid);
for (const lcr_route of body) {
await addNewLcrRoute(lcr_route, lookupCarrierBySid);
}
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
}
});
router.put('/:sid/Routes', async(req, res) => {
const results = await Lcr.retrieve(req.params.sid);
if (results.length === 0) return res.sendStatus(404);
const {logger, lookupCarrierBySid} = req.app.locals;
try {
const body = req.body;
await validateLcrBatchAdd(req.params.sid, body, lookupCarrierBySid);
for (const lcr_route of body) {
if (lcr_route.lcr_route_sid) {
const lcr_route_sid = lcr_route.lcr_route_sid;
delete lcr_route.lcr_route_sid;
const lcr_carrier_set_entries = lcr_route.lcr_carrier_set_entries;
delete lcr_route.lcr_carrier_set_entries;
await LcrRoutes.update(lcr_route_sid, lcr_route);
for (const entry of lcr_carrier_set_entries) {
const lcr_carrier_set_entry_sid = entry.lcr_carrier_set_entry_sid;
delete entry.lcr_carrier_set_entry_sid;
await LcrCarrierSetEntry.update(lcr_carrier_set_entry_sid, entry);
}
} else {
// Route is not available yet, let create it now
await addNewLcrRoute(lcr_route, lookupCarrierBySid);
}
}
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
}
});
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
try {

View File

@@ -13,8 +13,6 @@ WHERE up.permission_sid = p.permission_sid
AND up.user_sid = ?
`;
const retrieveSql = 'SELECT * from users where name = ?';
const tokenSql = 'SELECT token from api_keys where account_sid IS NULL AND service_provider_sid IS NULL';
router.post('/', async(req, res) => {
const {logger, incrKey, retrieveKey} = req.app.locals;
@@ -31,6 +29,9 @@ router.post('/', async(req, res) => {
return res.sendStatus(403);
}
logger.info({r}, 'successfully retrieved user account');
if (r[0].provider !== 'local') {
return res.sendStatus(403);
}
const maxLoginAttempts = process.env.LOGIN_ATTEMPTS_MAX_RETRIES || 6;
const loginAttempsBlocked = await retrieveKey(`login:${r[0].user_sid}`) >= maxLoginAttempts;
@@ -54,11 +55,6 @@ router.post('/', async(req, res) => {
return res.sendStatus(403);
}
const force_change = !!r[0].force_change;
const [t] = await promisePool.query(tokenSql);
if (t.length === 0) {
logger.error('Database has no admin token provisioned...run reset_admin_password');
return res.sendStatus(500);
}
const [p] = await promisePool.query(retrievePemissionsSql, r[0].user_sid);
const permissions = p.map((x) => x.name);
@@ -78,9 +74,13 @@ router.post('/', async(req, res) => {
obj.service_provider_sid = r[0].service_provider_sid;
obj.service_provider_name = service_provider[0].name;
}
// if there is only one permission and it is VIEW_ONLY, then the user is view only
// this is to prevent write operations on the API
const is_view_only = permissions.length === 1 && permissions.includes('VIEW_ONLY');
const payload = {
scope: obj.scope,
permissions,
is_view_only,
...(obj.service_provider_sid && {
service_provider_sid: obj.service_provider_sid,
service_provider_name: obj.service_provider_name
@@ -90,7 +90,8 @@ router.post('/', async(req, res) => {
account_name: obj.account_name,
service_provider_name: obj.service_provider_name
}),
user_sid: obj.user_sid
user_sid: obj.user_sid,
name: username
};
const expiresIn = parseInt(process.env.JWT_EXPIRES_IN || 60) * 60;

View File

@@ -40,6 +40,10 @@ async function validateAdd(req) {
if (!result || result.length === 0) {
throw new DbErrorBadRequest(`voip_carrier not found for sid ${req.body.voip_carrier_sid}`);
}
const carrier = result[0];
if (carrier.account_sid && req.body.account_sid && req.body.account_sid !== carrier.account_sid) {
throw new DbErrorBadRequest('voip_carrier_sid does not belong to the account');
}
}
}

View File

@@ -4,6 +4,7 @@ const {DbErrorBadRequest} = require('../../utils/errors');
const {getHomerApiKey, getHomerSipTrace, getHomerPcap} = require('../../utils/homer-utils');
const {getJaegerTrace} = require('../../utils/jaeger-utils');
const Account = require('../../models/account');
const { CloudWatchLogsClient, FilterLogEventsCommand } = require('@aws-sdk/client-cloudwatch-logs');
const {
getS3Object,
getGoogleStorageObject,
@@ -31,7 +32,7 @@ router.get('/', async(req, res) => {
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
const {page, count, trunk, direction, days, answered, start, end, filter} = req.query || {};
if (!page || page < 1) throw new DbErrorBadRequest('missing or invalid "page" query arg');
if (!count || count < 25 || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
if (!count || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
if (account_sid) {
const data = await queryCdrs({
@@ -106,6 +107,71 @@ router.get('/:call_id/:method/pcap', async(req, res) => {
}
});
router.get('/:call_sid/logs', async(req, res) => {
const {logger, queryCdrs} = req.app.locals;
const aws_region = process.env.AWS_REGION;
const {call_sid} = req.params;
const {logGroupName = 'jambonz-feature_server'} = req.query;
const account_sid = parseAccountSid(req.originalUrl);
if (!aws_region) {
return res.status(400).send({msg: 'Logs are only available in AWS environments'});
}
if (!account_sid) {
return res.status(400).send({msg: 'account_sid is required,' +
'please use /Accounts/{account_sid}/RecentCalls/{call_sid}/logs'});
}
try {
//find back the call in CDR to get timestame of the call
// this allow us limit search in cloudwatch logs
const data = await queryCdrs({
account_sid,
filter: call_sid,
page: 0,
page_size: 50
});
if (!data || data.data.length === 0) {
return res.status(404).send({msg: 'Call not found'});
}
const {
attempted_at, //2025-02-24T13:11:51.969Z
terminated_at, //2025-02-24T13:11:56.153Z
sip_callid
} = data.data[0];
const TIMEBUFFER = 60; //60 seconds
const startTime = new Date(attempted_at).getTime() - TIMEBUFFER * 1000;
const endTime = new Date(terminated_at).getTime() + TIMEBUFFER * 1000;
const client = new CloudWatchLogsClient({ region: aws_region });
let params = {
logGroupName,
startTime,
endTime,
filterPattern: `{ ($.callSid = "${call_sid}") || ($.callId = "${sip_callid}") }`
};
const command = new FilterLogEventsCommand(params);
const response = await client.send(command);
// if response have nextToken, we need to fetch all logs
while (response.nextToken) {
params = {
...params,
nextToken: response.nextToken
};
const command = new FilterLogEventsCommand(params);
const response2 = await client.send(command);
response.events = response.events.concat(response2.events);
response.nextToken = response2.nextToken;
}
let logs = [];
if (response.events && response.events.length > 0) {
logs = response.events.map((e) => e.message);
}
res.status(200).json(logs);
} catch (err) {
logger.error({err}, 'Cannot fetch logs from cloudwatch');
res.status(500).send({msg: err.message});
}
});
router.get('/trace/:trace_id', async(req, res) => {
const {logger} = req.app.locals;
const {trace_id} = req.params;

View File

@@ -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 */

View File

@@ -4,6 +4,7 @@ const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
//const {parseSipGatewaySid} = require('./utils');
const decorate = require('./decorate');
const sysError = require('../error');
const net = require('net');
const checkUserScope = async(req, voip_carrier_sid) => {
const {lookupCarrierBySid} = req.app.locals;
@@ -41,6 +42,7 @@ const checkUserScope = async(req, voip_carrier_sid) => {
const validate = async(req, sid) => {
const {lookupSipGatewayBySid} = req.app.locals;
const {netmask, ipv4, inbound, outbound} = req.body;
let voip_carrier_sid;
if (sid) {
@@ -52,6 +54,18 @@ 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}`);
}
if (inbound && !net.isIPv4(ipv4)) {
throw new DbErrorBadRequest('Inbound gateway must be IPv4 address');
}
if (!inbound && outbound && (netmask && netmask != 32)) {
throw new DbErrorBadRequest('For outbound only gateway netmask can only be 32');
}
await checkUserScope(req, voip_carrier_sid);
};

View File

@@ -31,6 +31,7 @@ router.post('/:sip_realm', async(req, res) => {
const [sbcs] = await promisePool.query('SELECT ipv4 from sbc_addresses');
if (sbcs.length === 0) throw new Error('no SBC addresses provisioned in the database!');
const ips = sbcs.map((s) => s.ipv4);
const uniqueIps = [...new Set(ips)];
/* retrieve existing dns records */
const [old_recs] = await promisePool.query('SELECT record_id from dns_records WHERE account_sid = ?',
@@ -48,7 +49,7 @@ router.post('/:sip_realm', async(req, res) => {
}
/* add the dns records */
const records = await createDnsRecords(logger, domain, subdomain, ips);
const records = await createDnsRecords(logger, domain, subdomain, uniqueIps);
if (!records) throw new Error(`failure updating dns records for ${sip_realm}`);
const values = records.map((r) => {
return `('${uuid()}', '${account_sid}', '${r.type}', ${r.id})`;

View File

@@ -3,9 +3,19 @@ const assert = require('assert');
const Account = require('../../models/account');
const SpeechCredential = require('../../models/speech-credential');
const sysError = require('../error');
const {decrypt, encrypt, obscureKey} = require('../../utils/encrypt-decrypt');
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
const {parseAccountSid, parseServiceProviderSid, parseSpeechCredentialSid} = require('./utils');
const {DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors');
const {decryptCredential, testWhisper, testDeepgramTTS,
getLanguagesAndVoicesForVendor,
testPlayHT,
testRimelabs,
testVerbioTts,
testVerbioStt,
testSpeechmaticsStt,
testCartesia,
testVoxistStt,
testOpenAiStt} = require('../../utils/speech-utils');
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
const {
testGoogleTts,
testGoogleStt,
@@ -19,7 +29,9 @@ const {
testDeepgramStt,
testSonioxStt,
testIbmTts,
testIbmStt
testIbmStt,
testElevenlabs,
testAssemblyStt
} = require('../../utils/speech-utils');
const {promisePool} = require('../../db');
@@ -107,11 +119,17 @@ const encryptCredential = (obj) => {
secret_access_key,
aws_region,
api_key,
role_arn,
region,
client_id,
client_secret,
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,
@@ -126,7 +144,14 @@ const encryptCredential = (obj) => {
instance_id,
custom_stt_url,
custom_tts_url,
auth_token = ''
custom_tts_streaming_url,
auth_token = '',
cobalt_server_uri,
model_id,
user_id,
voice_engine,
engine_version,
options
} = obj;
switch (vendor) {
@@ -142,10 +167,17 @@ const encryptCredential = (obj) => {
return encrypt(service_key);
case 'aws':
assert(access_key_id, 'invalid aws speech credential: access_key_id is required');
assert(secret_access_key, 'invalid aws speech credential: secret_access_key is required');
assert(aws_region, 'invalid aws speech credential: aws_region is required');
const awsData = JSON.stringify({aws_region, access_key_id, secret_access_key});
// AWS polly can work for 3 types of credentials:
// 1/ access_key_id and secret_access_key
// 2/ RoleArn Assume role
// 3/ RoleArn assigned to instance profile where will run this application
const awsData = JSON.stringify(
{
aws_region,
...(access_key_id && {access_key_id}),
...(secret_access_key && {secret_access_key}),
...(role_arn && {role_arn}),
});
return encrypt(awsData);
case 'microsoft':
@@ -178,8 +210,11 @@ const encryptCredential = (obj) => {
return encrypt(nuanceData);
case 'deepgram':
assert(api_key, 'invalid deepgram speech credential: api_key is required');
const deepgramData = JSON.stringify({api_key});
// API key is optional if onprem
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, deepgram_tts_uri});
return encrypt(deepgramData);
case 'ibm':
@@ -196,9 +231,74 @@ const encryptCredential = (obj) => {
const sonioxData = JSON.stringify({api_key});
return encrypt(sonioxData);
case 'cobalt':
assert(cobalt_server_uri, 'invalid cobalt speech credential: cobalt_server_uri is required');
const cobaltData = JSON.stringify({cobalt_server_uri});
return encrypt(cobaltData);
case 'elevenlabs':
assert(api_key, 'invalid elevenLabs speech credential: api_key is required');
assert(model_id, 'invalid elevenLabs speech credential: model_id is required');
const elevenlabsData = JSON.stringify({api_key, model_id, 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');
assert(voice_engine, 'invalid voice_engine speech credential: voice_engine is required');
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');
const rimelabsData = JSON.stringify({api_key, model_id, options});
return encrypt(rimelabsData);
case 'assemblyai':
assert(api_key, 'invalid assemblyai speech credential: api_key is required');
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');
const whisperData = JSON.stringify({api_key, model_id});
return encrypt(whisperData);
case 'openai':
assert(api_key, 'invalid openai speech credential: api_key is required');
assert(model_id, 'invalid openai speech credential: model_id is required');
const openaiData = JSON.stringify({api_key, model_id});
return encrypt(openaiData);
case 'verbio':
assert(engine_version, 'invalid verbio speech credential: client_id is required');
assert(client_id, 'invalid verbio speech credential: client_id is required');
assert(client_secret, 'invalid verbio speech credential: secret is required');
const verbioData = JSON.stringify({client_id, client_secret, engine_version});
return encrypt(verbioData);
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}`);
@@ -230,7 +330,7 @@ router.post('/', async(req, res) => {
// Check if vendor and label is already used for account or SP
if (label) {
const existingSpeech = await SpeechCredential.isAvailableVendorAndLabel(
const existingSpeech = await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
service_provider_sid, account_sid, vendor, label);
if (existingSpeech.length > 0) {
throw new DbErrorUnprocessableRequest(`Label ${label} is already in use for another speech credential`);
@@ -279,68 +379,7 @@ router.get('/', async(req, res) => {
res.status(200).json(creds.map((c) => {
const {credential, ...obj} = c;
if ('google' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
const key_header = '-----BEGIN PRIVATE KEY-----\n';
const obscured = {
...o,
private_key: `${key_header}${obscureKey(o.private_key.slice(key_header.length, o.private_key.length))}`
};
obj.service_key = obscured;
}
else if ('aws' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.secret_access_key = obscureKey(o.secret_access_key);
obj.aws_region = o.aws_region;
logger.info({obj, o}, 'retrieving aws speech credential');
}
else if ('microsoft' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
obj.region = o.region;
obj.use_custom_tts = o.use_custom_tts;
obj.custom_tts_endpoint = o.custom_tts_endpoint;
obj.custom_tts_endpoint_url = o.custom_tts_endpoint_url;
obj.use_custom_stt = o.use_custom_stt;
obj.custom_stt_endpoint = o.custom_stt_endpoint;
obj.custom_stt_endpoint_url = o.custom_stt_endpoint_url;
logger.info({obj, o}, 'retrieving azure speech credential');
}
else if ('wellsaid' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
else if ('nuance' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.client_id = o.client_id;
obj.secret = o.secret ? obscureKey(o.secret) : null;
}
else if ('deepgram' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
else if ('ibm' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.tts_api_key = obscureKey(o.tts_api_key);
obj.tts_region = o.tts_region;
obj.stt_api_key = obscureKey(o.stt_api_key);
obj.stt_region = o.stt_region;
obj.instance_id = o.instance_id;
} else if ('nvidia' == obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.riva_server_uri = o.riva_server_uri;
}
else if ('soniox' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
else if (obj.vendor.startsWith('custom:')) {
const o = JSON.parse(decrypt(credential));
obj.auth_token = obscureKey(o.auth_token);
obj.custom_stt_url = o.custom_stt_url;
obj.custom_tts_url = o.custom_tts_url;
}
decryptCredential(obj, credential, logger);
if (req.user.hasAccountAuth && obj.account_sid === null) {
delete obj.api_key;
@@ -370,68 +409,7 @@ router.get('/:sid', async(req, res) => {
await validateRetrieveUpdateDelete(req, cred);
const {credential, ...obj} = cred[0];
if ('google' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
const key_header = '-----BEGIN PRIVATE KEY-----\n';
const obscured = {
...o,
private_key: `${key_header}${obscureKey(o.private_key.slice(key_header.length, o.private_key.length))}`
};
obj.service_key = JSON.stringify(obscured);
}
else if ('aws' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.secret_access_key = obscureKey(o.secret_access_key);
obj.aws_region = o.aws_region;
}
else if ('microsoft' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
obj.region = o.region;
obj.use_custom_tts = o.use_custom_tts;
obj.custom_tts_endpoint = o.custom_tts_endpoint;
obj.custom_tts_endpoint_url = o.custom_tts_endpoint_url;
obj.use_custom_stt = o.use_custom_stt;
obj.custom_stt_endpoint = o.custom_stt_endpoint;
obj.custom_stt_endpoint_url = o.custom_stt_endpoint_url;
}
else if ('wellsaid' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
else if ('nuance' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.client_id = o.client_id;
obj.secret = o.secret ? obscureKey(o.secret) : null;
obj.nuance_tts_uri = o.nuance_tts_uri;
obj.nuance_stt_uri = o.nuance_stt_uri;
}
else if ('deepgram' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
else if ('ibm' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.tts_api_key = obscureKey(o.tts_api_key);
obj.tts_region = o.tts_region;
obj.stt_api_key = obscureKey(o.stt_api_key);
obj.stt_region = o.stt_region;
obj.instance_id = o.instance_id;
} else if ('nvidia' == obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.riva_server_uri = o.riva_server_uri;
}
else if ('soniox' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
else if (obj.vendor.startsWith('custom:')) {
const o = JSON.parse(decrypt(credential));
obj.auth_token = obscureKey(o.auth_token);
obj.custom_stt_url = o.custom_stt_url;
obj.custom_tts_url = o.custom_tts_url;
}
decryptCredential(obj, credential, logger);
if (req.user.hasAccountAuth && obj.account_sid === null) {
delete obj.api_key;
@@ -503,7 +481,17 @@ router.put('/:sid', async(req, res) => {
custom_stt_endpoint,
custom_stt_endpoint_url,
custom_stt_url,
custom_tts_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,
speechmatics_stt_uri
} = req.body;
const newCred = {
@@ -523,7 +511,17 @@ router.put('/:sid', async(req, res) => {
nuance_stt_uri,
nuance_tts_uri,
custom_stt_url,
custom_tts_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,
speechmatics_stt_uri
};
logger.info({o, newCred}, 'updating speech credential with this new credential');
obj.credential = encryptCredential(newCred);
@@ -552,7 +550,7 @@ router.put('/:sid', async(req, res) => {
* Test a credential
*/
router.get('/:sid/test', async(req, res) => {
const logger = req.app.locals.logger;
const {logger, synthAudio, getVerbioAccessToken} = req.app.locals;
try {
const sid = parseSpeechCredentialSid(req);
const creds = await SpeechCredential.retrieve(sid);
@@ -600,12 +598,13 @@ router.get('/:sid/test', async(req, res) => {
}
}
else if (cred.vendor === 'aws') {
const {getTtsVoices, getAwsAuthToken} = req.app.locals;
if (cred.use_for_tts) {
const {getTtsVoices} = req.app.locals;
try {
await testAwsTts(logger, getTtsVoices, {
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || process.env.AWS_REGION
});
results.tts.status = 'ok';
@@ -617,9 +616,10 @@ router.get('/:sid/test', async(req, res) => {
}
if (cred.use_for_stt) {
try {
await testAwsStt(logger, {
await testAwsStt(logger, getAwsAuthToken, {
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || process.env.AWS_REGION
});
results.stt.status = 'ok';
@@ -643,7 +643,7 @@ router.get('/:sid/test', async(req, res) => {
} = credential;
if (cred.use_for_tts) {
try {
await testMicrosoftTts(logger, {
await testMicrosoftTts(logger, synthAudio, {
api_key,
region,
use_custom_tts,
@@ -662,7 +662,7 @@ router.get('/:sid/test', async(req, res) => {
}
if (cred.use_for_stt) {
try {
await testMicrosoftStt(logger, {api_key, region});
await testMicrosoftStt(logger, {api_key, region, use_custom_stt, custom_stt_endpoint_url});
results.stt.status = 'ok';
SpeechCredential.sttTestResult(sid, true);
} catch (err) {
@@ -721,10 +721,19 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.sttTestResult(sid, false);
}
}
}
else if (cred.vendor === 'deepgram') {
} else if (cred.vendor === 'deepgram') {
const {api_key} = credential;
if (cred.use_for_stt) {
if (cred.use_for_tts) {
try {
await testDeepgramTTS(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
if (cred.use_for_stt && api_key) {
try {
await testDeepgramStt(logger, {api_key});
results.stt.status = 'ok';
@@ -780,6 +789,140 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.sttTestResult(sid, false);
}
}
} else if (cred.vendor === 'elevenlabs') {
const {api_key, model_id} = credential;
if (cred.use_for_tts) {
try {
await testElevenlabs(logger, {api_key, model_id});
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
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 {
await testPlayHT(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 === '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 {
await testRimelabs(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'assemblyai') {
const {api_key} = credential;
if (cred.use_for_stt) {
try {
await testAssemblyStt(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 === '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 {
await testWhisper(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'openai') {
if (cred.use_for_stt) {
try {
await testOpenAiStt(logger, credential);
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 === 'verbio') {
if (cred.use_for_tts) {
try {
await testVerbioTts(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
if (cred.use_for_stt) {
try {
await testVerbioStt(logger, getVerbioAccessToken, credential);
results.stt.status = 'ok';
SpeechCredential.sttTestResult(sid, true);
} catch (err) {
results.stt = {status: 'fail', reason: err.message};
SpeechCredential.sttTestResult(sid, false);
}
}
}
res.status(200).json(results);
@@ -789,4 +932,34 @@ router.get('/:sid/test', async(req, res) => {
}
});
/**
* Fetch speech voices and languages
*/
router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
const {logger, getTtsVoices} = req.app.locals;
try {
const {vendor, label, create_new} = req.query;
if (!vendor) {
throw new DbErrorBadRequest('vendor is required');
}
const account_sid = req.user.account_sid || req.body.account_sid;
const service_provider_sid = req.user.service_provider_sid ||
req.body.service_provider_sid || parseServiceProviderSid(req);
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;
try {
const data = await getLanguagesAndVoicesForVendor(logger, vendor, cred, getTtsVoices);
res.status(200).json(data);
} catch (err) {
throw new DbErrorUnprocessableRequest(err.message);
}
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -2,6 +2,15 @@ const router = require('express').Router();
const {
parseAccountSid
} = require('./utils');
const SpeechCredential = require('../../models/speech-credential');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const {DbErrorBadRequest} = require('../../utils/errors');
const Account = require('../../models/account');
const sysError = require('../error');
const { getSpeechCredential, decryptCredential } = require('../../utils/speech-utils');
const PCMToMP3Encoder = require('../../record/encoder');
const { pipeline } = require('stream');
router.delete('/', async(req, res) => {
const {purgeTtsCache} = req.app.locals;
@@ -26,4 +35,103 @@ router.get('/', async(req, res) => {
res.status(200).json({size});
});
router.post('/Synthesize', async(req, res) => {
const {logger, synthAudio} = req.app.locals;
try {
const accountSid = parseAccountSid(req);
const body = req.body;
const encodingMp3 = req.body.encodingMp3 || false;
if (!body.speech_credential_sid || !body.text || !body.language || !body.voice) {
throw new DbErrorBadRequest('speech_credential_sid, text, language, voice are all required');
}
const result = await Account.retrieve(accountSid);
if (!result || result.length === 0 || !result[0].is_active) {
throw new DbErrorBadRequest(`Account not found for sid ${accountSid}`);
}
const credentials = await SpeechCredential.retrieve(body.speech_credential_sid);
if (!credentials || credentials.length === 0) {
throw new
DbErrorBadRequest(`There is no available speech credential for ${body.speech_credential_sid}`);
}
const {credential, ...obj} = credentials[0];
decryptCredential(obj, credential, logger, false);
const cred = getSpeechCredential(obj, logger);
const { text, language, engine = 'standard' } = body;
const salt = uuidv4();
/* parse Nuance voices into name and model */
let voice = body.voice;
let model;
if (cred.vendor === 'nuance' && voice) {
const arr = /([A-Za-z-]*)\s+-\s+(enhanced|standard)/.exec(voice);
if (arr) {
voice = arr[1];
model = arr[2];
}
} else if (cred.vendor === 'deepgram') {
model = voice;
}
const stats = {
histogram: () => {},
increment: () => {},
};
const { filePath } = await synthAudio(stats, {
account_sid: accountSid,
text,
vendor: cred.vendor,
language,
voice,
engine,
model,
salt,
credentials: cred,
disableTtsCache: false,
disableTtsStreaming: true
});
let contentType = 'audio/mpeg';
let readStream = fs.createReadStream(filePath);
if (['nuance', 'nvidia'].includes(cred.vendor) ||
(
process.env.JAMBONES_TTS_TRIM_SILENCE &&
['microsoft', 'azure'].includes(cred.vendor)
)
) {
if (encodingMp3) {
readStream = readStream
.pipe(new PCMToMP3Encoder({
channels: 1,
sampleRate: 8000,
bitRate: 128
}, logger));
} else {
contentType = 'application/octet-stream';
}
}
res.writeHead(200, {
'Content-Type': contentType,
});
pipeline(readStream, res, (err) => {
if (err) {
logger.error('ttscache/Synthesize failed:', err);
if (!res.headersSent) {
res.status(500).end('Server error');
}
}
fs.unlink(filePath, (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
logger.info(`${filePath} was deleted`);
});
});
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -1,5 +1,7 @@
const router = require('express').Router();
const User = require('../../models/user');
const UserPermissions = require('../../models/user-permissions');
const Permissions = require('../../models/permissions');
const {DbErrorBadRequest, BadRequestError, DbErrorForbidden} = require('../../utils/errors');
const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils');
const {promisePool} = require('../../db');
@@ -38,7 +40,8 @@ const validateRequest = async(user_sid, req) => {
email,
email_activation_code,
force_change,
is_active
is_active,
is_view_only
} = payload;
const [r] = await promisePool.query(retrieveSql, user_sid);
@@ -93,7 +96,8 @@ const validateRequest = async(user_sid, req) => {
if (email_activation_code && !email) {
throw new DbErrorBadRequest('email and email_activation_code both required');
}
if (!name && !new_password && !email && !initial_password && !force_change && !is_active)
if (!name && !new_password && !email && !initial_password && !force_change && !is_active &&
is_view_only === undefined)
throw new DbErrorBadRequest('no updates requested');
return user;
@@ -140,7 +144,35 @@ const ensureUserRetrievalIsAllowed = (req, user) => {
throw error;
}
};
async function updateViewOnlyUserPermission(is_view_only, user_sid) {
try {
const [viewOnlyPermission] = await Permissions.retrieveByName('VIEW_ONLY');
if (!viewOnlyPermission) {
throw new Error('VIEW_ONLY permission not found');
}
const existingPermissions = await UserPermissions.retrieveByUserIdPermissionSid(
user_sid,
viewOnlyPermission.permission_sid
);
if (is_view_only && existingPermissions.length === 0) {
await UserPermissions.make({
user_sid,
permission_sid: viewOnlyPermission.permission_sid,
});
} else if (!is_view_only && existingPermissions.length > 0) {
await UserPermissions.remove(existingPermissions[0].user_permissions_sid);
}
} catch (err) {
throw new Error(`Failed to update user permissions: ${err.message}`);
}
}
async function removeViewOnlyUserPermission(user_id) {
const [viewOnlyPermission] = await Permissions.retrieveByName('VIEW_ONLY');
if (viewOnlyPermission) {
await UserPermissions.remove(user_id, viewOnlyPermission.permission_sid);
}
}
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
@@ -308,7 +340,14 @@ router.get('/:user_sid', async(req, res) => {
}
ensureUserRetrievalIsAllowed(req, user);
const [viewOnlyPermission] = await Permissions.retrieveByName('VIEW_ONLY');
const existingPermissions = await UserPermissions.retrieveByUserId(
user_sid
);
logger.debug(`existingPermissions of ${user_sid}: ${JSON.stringify(existingPermissions)}`);
user.is_view_only = existingPermissions.length === 1 &&
existingPermissions[0].permission_sid === viewOnlyPermission.permission_sid;
logger.debug(`User ${user_sid} is view-only user: ${user.is_view_only}`);
// eslint-disable-next-line no-unused-vars
const { hashed_password, ...rest } = user;
return res.status(200).json(rest);
@@ -332,9 +371,9 @@ router.put('/:user_sid', async(req, res) => {
is_active,
force_change,
account_sid,
service_provider_sid
service_provider_sid,
is_view_only
} = req.body;
//if (req.user.user_sid && req.user.user_sid !== user_sid) return res.sendStatus(403);
if (!hasAdminAuth &&
@@ -427,6 +466,8 @@ router.put('/:user_sid', async(req, res) => {
//TODO: send email with activation code
}
}
// update user permissions
await updateViewOnlyUserPermission(is_view_only, user_sid);
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
@@ -443,6 +484,8 @@ router.post('/', async(req, res) => {
};
const allUsers = await User.retrieveAll();
delete payload.initial_password;
const is_view_only = payload.is_view_only;
delete payload.is_view_only;
try {
if (req.body.initial_password) {
@@ -464,6 +507,7 @@ router.post('/', async(req, res) => {
if (req.user.hasAdminAuth) {
logger.debug({payload}, 'POST /users');
const uuid = await User.make(payload);
await updateViewOnlyUserPermission(is_view_only, uuid);
res.status(201).json({user_sid: uuid});
}
else if (req.user.hasAccountAuth) {
@@ -472,6 +516,7 @@ router.post('/', async(req, res) => {
...payload,
account_sid: req.user.account_sid,
});
await updateViewOnlyUserPermission(is_view_only, uuid);
res.status(201).json({user_sid: uuid});
}
else if (req.user.hasServiceProviderAuth) {
@@ -480,6 +525,7 @@ router.post('/', async(req, res) => {
...payload,
service_provider_sid: req.user.service_provider_sid,
});
await updateViewOnlyUserPermission(is_view_only, uuid);
res.status(201).json({user_sid: uuid});
}
} catch (err) {
@@ -497,6 +543,8 @@ router.delete('/:user_sid', async(req, res) => {
const user = allUsers.filter((user) => user.user_sid === user_sid);
ensureUserDeletionIsAllowed(req, activeAdminUsers, user);
logger.debug(`Removing view-only permission for user ${user_sid}`);
await removeViewOnlyUserPermission(user_sid);
await User.remove(user_sid);
/* invalidate the jwt of the deleted user */

View File

@@ -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,
};

View File

@@ -73,10 +73,12 @@ decorate(router, VoipCarrier, ['add', 'update', 'delete'], preconditions);
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
const {lookupAccountBySid} = req.app.locals;
const account = req.user.service_provider_sid ? req.user : await lookupAccountBySid(req.user.account_sid);
try {
const results = req.user.hasAdminAuth ?
await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null) :
await VoipCarrier.retrieveAllForSP(req.user.service_provider_sid);
await VoipCarrier.retrieveAllForSP(account.service_provider_sid);
if (req.user.hasScope('account')) {
return res.status(200).json(results.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));

View File

@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: jambonz REST API
description: jambonz REST API
title: Jambonz REST API
description: Jambonz REST API specification
contact:
email: daveh@drachtio.org
license:
@@ -44,6 +44,8 @@ tags:
description: Least Cost Routing Routes operations
- name: LcrCarrierSetEntries
description: Least Cost Routing Carrier Set Entries operation
- name: GoogleCustomVoices
description: Google Custom voices operation
paths:
/BetaInviteCodes:
post:
@@ -380,11 +382,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/login:
post:
tags:
- Authentication
summary: login and retrieve a JWT
operationId: login
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Login'
responses:
200:
description: user logged in
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulLogin'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/logout:
post:
tags:
- Authentication
summary: log out and deactivate jwt
summary: log out and deactivate the JWT
operationId: logoutUser
responses:
204:
@@ -582,10 +608,9 @@ paths:
content:
application/json:
schema:
type:
array
type: array
items:
$ref: '#/components/schemas/Users'
$ref: '#/components/schemas/UserList'
403:
description: unauthorized
500:
@@ -608,27 +633,13 @@ paths:
- Users
summary: retrieve user information
operationId: getUser
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
email:
type: string
is_active:
type: boolean
force_change:
type: boolean
scope:
type: string
permissions:
type: array
responses:
204:
200:
description: user information
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
403:
description: user information
content:
@@ -672,6 +683,8 @@ paths:
type: string
permissions:
type: array
items:
type: string
responses:
204:
description: user updated
@@ -710,6 +723,8 @@ paths:
type: string
permissions:
type: array
items:
type: string
old_password:
type: string
description: existing password, which is to be replaced
@@ -996,7 +1011,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/AccountTest/:ServiceProviderSid:
/AccountTest/{ServiceProviderSid}:
parameters:
- name: ServiceProviderSid
in: path
@@ -1093,6 +1108,9 @@ paths:
requires_register:
type: boolean
description: wehther this provider requires us to send a REGISTER to them in order to receive calls
register_use_tls:
type: boolean
description: wehther this provider requires us to send a REGISTER use TLS protocol
register_username:
type: string
description: sip username to authenticate with, if registration is required
@@ -1969,7 +1987,7 @@ paths:
tags:
- Service Providers
summary: add a VoiPCarrier to a service provider based on PredefinedCarrier template
operationId: createVoipCarrierFromTemplate
operationId: createVoipCarrierFromTemplateBySP
responses:
201:
description: voip carrier successfully created
@@ -2070,6 +2088,41 @@ paths:
description: credential successfully deleted
404:
description: credential not found
/ServiceProviders/{ServiceProviderSid}/SpeechCredentials/speech/supportedLanguagesAndVoices:
get:
tags:
- Service Providers
summary: get supported languages, voices and models
operationId: supportedLanguagesAndVoices
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
- name: vendor
in: query
required: true
schema:
type: string
- name: label
in: query
schema:
type: string
responses:
200:
description: get supported languages, voices and models
content:
application/json:
schema:
$ref: '#/components/schemas/SpeechLanguagesVoices'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/ServiceProviders/{ServiceProviderSid}/SpeechCredentials/{SpeechCredentialSid}/test:
get:
tags:
@@ -2889,7 +2942,7 @@ paths:
tags:
- Accounts
summary: get a specific speech credential
operationId: getSpeechCredential
operationId: getSpeechCredentialByAccount
responses:
200:
description: retrieve speech credentials for a specified account
@@ -2903,7 +2956,7 @@ paths:
tags:
- Accounts
summary: update a speech credential
operationId: updateSpeechCredential
operationId: updateSpeechCredentialByAccount
requestBody:
content:
application/json:
@@ -2924,18 +2977,53 @@ paths:
tags:
- Accounts
summary: delete a speech credential
operationId: deleteSpeechCredential
operationId: deleteSpeechCredentialByAccount
responses:
204:
description: credential successfully deleted
404:
description: credential not found
/Accounts/{AccountSid}/SpeechCredentials/speech/supportedLanguagesAndVoices:
get:
tags:
- Accounts
summary: get supported languages, voices and models
operationId: supportedLanguagesAndVoicesByAccount
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
- name: vendor
in: query
required: true
schema:
type: string
- name: label
in: query
schema:
type: string
responses:
200:
description: get supported languages, voices and models
content:
application/json:
schema:
$ref: '#/components/schemas/SpeechLanguagesVoices'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/SpeechCredentials/{SpeechCredentialSid}/test:
get:
tags:
- Accounts
summary: test a speech credential
operationId: testSpeechCredential
operationId: testSpeechCredentialByAccount
parameters:
- name: AccountSid
in: path
@@ -3181,7 +3269,7 @@ paths:
tags:
- Service Providers
summary: retrieve pcap for a call
operationId: getRecentCallTrace
operationId: getRecentCallTraceBySP
responses:
200:
description: retrieve sip trace data
@@ -3267,7 +3355,7 @@ paths:
tags:
- Service Providers
summary: retrieve recent calls for an account
operationId: listRecentCalls
operationId: listRecentCallsBySP
responses:
200:
description: retrieve recent call records for a specified account
@@ -3368,7 +3456,7 @@ paths:
tags:
- Service Providers
summary: retrieve sip trace detail for a call
operationId: getRecentCallTrace
operationId: getRecentCallTraceByCallId
responses:
200:
description: retrieve sip trace data
@@ -3395,7 +3483,7 @@ paths:
tags:
- Accounts
summary: retrieve pcap for a call
operationId: getRecentCallTrace
operationId: getRecentCallTraceByAccount
responses:
200:
description: retrieve sip trace data
@@ -3581,7 +3669,7 @@ paths:
tags:
- Accounts
summary: retrieve alerts for an account
operationId: listAlerts
operationId: listAlertsByAccount
responses:
200:
description: retrieve alerts for a specified account
@@ -3794,10 +3882,38 @@ paths:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Conferences:
get:
tags:
- Conferences
summary: list conferences
operationId: listConferences
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
responses:
200:
description: list of conferences for a specified account
content:
application/json:
schema:
type: array
items:
type: string
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Calls:
post:
tags:
- Accounts
- Accounts
summary: create a call
operationId: createCall
parameters:
@@ -3856,6 +3972,10 @@ paths:
type: object
description: The customer SIP headers to associate with the call
example: {"X-Custom-Header": "Hello"}
sipRequestWithinDialogHook:
type: string
description: The sip indialog hook to receive session messages
example: '/customHook'
responses:
201:
description: call successfully created
@@ -4060,6 +4180,22 @@ paths:
type: string
siprecServerURL:
type: string
conferenceParticipantAction:
type: object
properties:
action:
type: string
enum:
- tag
- untag
- coach
- uncoach
- mute
- unmute
- hold
- unhold
tag:
type: string
responses:
200:
description: Accepted
@@ -4164,6 +4300,146 @@ paths:
type: string
length:
type: string
/Accounts/{AccountSid}/RegisteredSipUsers:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
get:
tags:
- Accounts
summary: retrieve online sip users for an account
operationId: listRegisteredSipUsers
responses:
200:
description: retrieve online sip users for an account
content:
application/json:
schema:
type: array
items:
type: string
post:
tags:
- Accounts
summary: retrieve online sip users for an account by list of sip username
operationId: listRegisteredSipUsersByUsername
requestBody:
content:
application/json:
schema:
type: array
items:
type: string
responses:
200:
description: retrieve online sip users for an account
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RegisteredClient'
/Accounts/{AccountSid}/RegisteredSipUsers/{Client}:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
- name: Client
in: path
required: true
style: simple
explode: false
schema:
type: string
get:
tags:
- Accounts
summary: retrieve registered client registration
operationId: getRegisteredClient
responses:
200:
description: registered client found
content:
application/json:
schema:
$ref: '#/components/schemas/RegisteredClient'
/Accounts/{AccountSid}/TtsCache/Synthesize:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
post:
tags:
- Accounts
summary: get TTS from provider
operationId: Synthesize
requestBody:
content:
application/json:
schema:
type: object
properties:
speech_credential_sid:
type: string
description: Speech credential Sid
example: 553b4b6b-8918-4394-a46d-1e3c5a3c717b
text:
type: string
description: the text to convert to audio
example: Hello How are you
language:
type: string
description: language is used in text
example: en-US
voice:
type: string
description: voice ID
example: en-US-Standard-C
encodingMp3:
type: boolean
description: convert audio to mp3.
example: true
required:
- speech_credential_sid
- text
- language
- voice
responses:
200:
description: Audio is created
content:
audio/mpeg:
schema:
type: string
format: binary
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
422:
description: unprocessable entity
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Lcrs:
post:
tags:
@@ -4311,6 +4587,69 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Lcrs/{LcrSid}/Routes:
parameters:
- name: LcrSid
in: path
required: true
style: simple
explode: false
schema:
type: string
post:
tags:
- Lcrs
summary: Create least cost routing routes and carrier set entries
operationId: createLeastCostRoutingRoutesAndCarrierEntries
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/LcrRoutes'
responses:
204:
description: least cost routing routes and carrier set entries created
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
404:
description: least cost routing not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
put:
tags:
- Lcrs
summary: update least cost routing routes and carrier set entries
operationId: updateLeastCostRoutingRoutesAndCarrierEntries
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/LcrRoutes'
responses:
204:
description: least cost routing ruoutes and carrier entries updated
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
404:
description: least cost routing not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/LcrRoutes:
post:
tags:
@@ -4603,6 +4942,173 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/GoogleCustomVoices:
post:
tags:
- GoogleCustomVoices
summary: create a Google custom voice
operationId: createGoogleCustomVoice
requestBody:
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/GoogleCustomVoice'
required:
- speech_credential_sid
- name
- reported_usage
- model
responses:
201:
description: Least Cost Routing Carrier Set Entry successfully created
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulAdd'
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
422:
description: unprocessable entity
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
tags:
- GoogleCustomVoices
parameters:
- in: query
name: service_provider_sid
required: false
schema:
type: string
description: return only the google voice custom operated belong to this service provider
- in: query
name: account_sid
required: false
schema:
type: string
description: return only the google voice custom operated belong to this account_sid
- in: query
name: speech_credential_sid
required: false
schema:
type: string
description: return only the google voice custom operated belong to this speech credential
summary: list google custom voices
operationId: listGoogleCustomVoices
responses:
200:
description: list oflist google custom voices
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/GoogleCustomVoice'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/GoogleCustomVoices/{GoogleCustomVoiceSid}:
parameters:
- name: GoogleCustomVoiceSid
in: path
required: true
style: simple
explode: false
schema:
type: string
delete:
tags:
- GoogleCustomVoices
summary: delete a google custom voice
operationId: deleteGoogleCustomVoice
responses:
204:
description: google custom voice successfully deleted
404:
description: google custom voice not found
422:
description: unprocessable entity
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
example:
msg: a service provider with active accounts can not be deleted
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
tags:
- GoogleCustomVoices
summary: retrieve google custom voice
operationId: getGoogleCustomVoice
responses:
200:
description: google custom voice found
content:
application/json:
schema:
$ref: '#/components/schemas/GoogleCustomVoice'
404:
description: google custom voice not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
put:
tags:
- GoogleCustomVoices
summary: update google custom voice
operationId: updateGoogleCustomVoice
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/GoogleCustomVoice'
responses:
204:
description: google custom voice updated
content:
application/json:
schema:
$ref: '#/components/schemas/GoogleCustomVoice'
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
404:
description: least cost routing carrier set entry not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
components:
securitySchemes:
bearerAuth:
@@ -4610,17 +5116,32 @@ components:
scheme: bearer
bearerFormat: token
schemas:
SuccessfulLogin:
type: object
required:
- username
- password
properties:
token:
type: string
user_sid:
type: string
scope:
type: string
force_change:
type: boolean
Login:
type: object
properties:
user_sid:
username:
type: string
api_token:
type: string
change_password:
type: boolean
password:
type: string
required:
- user_sid
- username
- password
SuccessfulApiKeyAdd:
type: object
required:
@@ -5561,6 +6082,137 @@ components:
- lcr_route_sid
- voip_carrier_sid
- priority
LcrRouteAndCarrierEntries:
allOf:
- $ref: '#/components/schemas/LcrRoute'
- type: object
properties:
lcr_carrier_set_entries:
type: array
items:
$ref: '#/components/schemas/LcrCarrierSetEntry'
LcrRoutes:
type: array
items:
$ref: '#/components/schemas/LcrRouteAndCarrierEntries'
GoogleCustomVoice:
type: object
properties:
speech_credential_sid:
type: string
example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
name:
type: string
example: Sally
reported_usage:
type: string
example: REALTIME
model:
type: string
example: projects/12412312/locations/global/models/2134124123-2dbf-43be-9593-12314123
required:
- speech_credential_sid
- name
- reported_usage
- model
RegisteredClient:
type: object
properties:
name:
type: string
example: xhoaluu
contact:
type: string
example: sip:0dluqjt6@od41sl9jfc9m.invalid;transport=ws
expiryTime:
type: number
example: 1698981449173
protocol:
type: string
example: wss
allow_direct_app_calling:
type: number
example: 1
allow_direct_queue_calling:
type: number
example: 1
allow_direct_user_calling:
type: number
example: 1
registered_status:
type: string
enum:
- active
- inactive
required:
- speech_credential_sid
- name
- reported_usage
- model
TtsModel:
type: object
properties:
name:
type: string
example: Turbo v2
value:
type: string
example: eleven_turbo_v2
LanguageVoice:
type: object
properties:
name:
type: string
example: Standard-A (Female)
value:
type: string
example: ar-XA-Standard-A
LanguageVoices:
type: object
properties:
name:
type: string
example: English (US)
value:
type: string
example: en-US
voices:
type: array
items:
$ref: '#/components/schemas/LanguageVoice'
SpeechLanguagesVoices:
type: object
properties:
tts:
type: array
items:
$ref: '#/components/schemas/LanguageVoices'
stt:
type: array
items:
$ref: '#/components/schemas/LanguageVoice'
ttsModel:
type: array
items:
$ref: '#/components/schemas/TtsModel'
UserList:
type: object
properties:
name:
type: string
email:
type: string
is_active:
type: boolean
force_change:
type: boolean
scope:
type: string
permissions:
type: array
items:
type: string
security:
- bearerAuth: []

View File

@@ -89,7 +89,12 @@ const createDnsRecords = async(logger, domain, name, value, ttl = 3600) => {
const str = await res.text();
return JSON.parse(str);
}
logger.error({res}, 'Error creating records');
let body;
try {
body = await res.json();
} catch (err) {
}
logger.error({headers: res.headers, body}, `Error creating records, status ${res.statusCode}`);
} catch (err) {
logger.error({err}, 'Error retrieving domains');
}

View File

@@ -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,
};

View File

@@ -27,11 +27,17 @@ class DbErrorForbidden extends DbError {
super(msg);
}
}
class UserPermissionError extends Error {
constructor(msg) {
super(msg);
}
}
module.exports = {
BadRequestError,
DbError,
DbErrorBadRequest,
DbErrorUnprocessableRequest,
DbErrorForbidden
DbErrorForbidden,
UserPermissionError
};

View File

@@ -0,0 +1,22 @@
module.exports = [
{ name: 'Global English', value: 'en' },
{ name: 'Australian English', value: 'en_au' },
{ name: 'British English', value: 'en_uk' },
{ name: 'US English', value: 'en_us' },
{ name: 'Spanish', value: 'es' },
{ name: 'French', value: 'fr' },
{ name: 'German', value: 'de' },
{ name: 'Italian', value: 'it' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Dutch', value: 'nl' },
{ name: 'Hindi', value: 'hi' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Chinese', value: 'zh' },
{ name: 'Finnish', value: 'fi' },
{ name: 'Korean', value: 'ko' },
{ name: 'Polish', value: 'pl' },
{ name: 'Russian', value: 'ru' },
{ name: 'Turkish', value: 'tr' },
{ name: 'Ukrainian', value: 'uk' },
{ name: 'Vietnamese', value: 'vi' },
];

View File

@@ -0,0 +1,10 @@
module.exports = [
{ name: 'Australian English', value: 'en-AU' },
{ name: 'British English', value: 'en-GB' },
{ name: 'US English', value: 'en-US' },
{ name: 'French', value: 'fr-FR' },
{ name: 'Canadian French', value: 'fr-CA' },
{ name: 'German', value: 'de-DE' },
{ name: 'Italian', value: 'it-IT' },
{ name: 'US Spanish', value: 'es-US' },
];

View File

@@ -0,0 +1,26 @@
module.exports = [
{
name: 'English US',
value: 'en_US-8khz',
},
{
name: 'English UK',
value: 'en_UK-8khz',
},
{
name: 'Spanish',
value: 'es_xx-8khz',
},
{
name: 'French',
value: 'fr_fr-8khz',
},
{
name: 'Russian',
value: 'ru_ru-8khz',
},
{
name: 'Portuguese',
value: 'pt_br-8khz',
},
];

View File

@@ -0,0 +1,56 @@
module.exports = [
{ name: 'Multilingual', value: 'multi' },
{ name: 'Bulgarian', value: 'bg' },
{ name: 'Catalan', value: 'ca' },
{ name: 'Chinese (Mandarin, Simplified)', value: 'zh' },
{ name: 'Chinese (Mandarin, Simplified - China)', value: 'zh-CN' },
{ name: 'Chinese (Mandarin, Simplified - Hans)', value: 'zh-Hans' },
{ name: 'Chinese (Mandarin, Traditional)', value: 'zh-TW' },
{ name: 'Chinese (Mandarin, Traditional - Hant)', value: 'zh-Hant' },
{ name: 'Chinese (Cantonese, Traditional - Hong Kong)', value: 'zh-HK' },
{ name: 'Czech', value: 'cs' },
{ name: 'Danish', value: 'da' },
{ name: 'Danish (Denmark)', value: 'da-DK' },
{ name: 'Dutch', value: 'nl' },
{ name: 'English', value: 'en' },
{ name: 'English (United States)', value: 'en-US' },
{ name: 'English (Australia)', value: 'en-AU' },
{ name: 'English (United Kingdom)', value: 'en-GB' },
{ name: 'English (New Zealand)', value: 'en-NZ' },
{ name: 'English (India)', value: 'en-IN' },
{ name: 'Estonian', value: 'et' },
{ name: 'Finnish', value: 'fi' },
{ name: 'Flemish', value: 'nl-BE' },
{ name: 'French', value: 'fr' },
{ name: 'French (Canada)', value: 'fr-CA' },
{ name: 'German', value: 'de' },
{ name: 'German (Switzerland)', value: 'de-CH' },
{ name: 'Greek', value: 'el' },
{ name: 'Hindi', value: 'hi' },
{ name: 'Hungarian', value: 'hu' },
{ name: 'Indonesian', value: 'id' },
{ name: 'Italian', value: 'it' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Korean', value: 'ko' },
{ name: 'Korean (South Korea)', value: 'ko-KR' },
{ name: 'Latvian', value: 'lv' },
{ name: 'Lithuanian', value: 'lt' },
{ name: 'Malay', value: 'ms' },
{ name: 'Norwegian', value: 'no' },
{ name: 'Polish', value: 'pl' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Portuguese (Brazil)', value: 'pt-BR' },
{ name: 'Portuguese (Portugal)', value: 'pt-PT' },
{ name: 'Romanian', value: 'ro' },
{ name: 'Russian', value: 'ru' },
{ name: 'Slovak', value: 'sk' },
{ name: 'Spanish', value: 'es' },
{ name: 'Spanish (Latin America)', value: 'es-419' },
{ name: 'Swedish', value: 'sv' },
{ name: 'Swedish (Sweden)', value: 'sv-SE' },
{ name: 'Thai', value: 'th' },
{ name: 'Thai (Thailand)', value: 'th-TH' },
{ name: 'Turkish', value: 'tr' },
{ name: 'Ukrainian', value: 'uk' },
{ name: 'Vietnamese', value: 'vi' }
];

View File

@@ -0,0 +1,130 @@
module.exports = [
{ name: 'Afrikaans (South Africa)', value: 'af-ZA' },
{ name: 'Albanian (Albania)', value: 'sq-AL' },
{ name: 'Amharic (Ethiopia)', value: 'am-ET' },
{ name: 'Arabic (Algeria)', value: 'ar-DZ' },
{ name: 'Arabic (Bahrain)', value: 'ar-BH' },
{ name: 'Arabic (Egypt)', value: 'ar-EG' },
{ name: 'Arabic (Iraq)', value: 'ar-IQ' },
{ name: 'Arabic (Israel)', value: 'ar-IL' },
{ name: 'Arabic (Jordan)', value: 'ar-JO' },
{ name: 'Arabic (Kuwait)', value: 'ar-KW' },
{ name: 'Arabic (Lebanon)', value: 'ar-LB' },
{ name: 'Arabic (Morocco)', value: 'ar-MA' },
{ name: 'Arabic (Oman)', value: 'ar-OM' },
{ name: 'Arabic (Qatar)', value: 'ar-QA' },
{ name: 'Arabic (Saudi Arabia)', value: 'ar-SA' },
{ name: 'Arabic (State of Palestine)', value: 'ar-PS' },
{ name: 'Arabic (Tunisia)', value: 'ar-TN' },
{ name: 'Arabic (United Arab Emirates)', value: 'ar-AE' },
{ name: 'Armenian (Armenia)', value: 'hy-AM' },
{ name: 'Azerbaijani (Azerbaijan)', value: 'az-AZ' },
{ name: 'Basque (Spain)', value: 'eu-ES' },
{ name: 'Bengali (Bangladesh)', value: 'bn-BD' },
{ name: 'Bengali (India)', value: 'bn-IN' },
{ name: 'Bulgarian (Bulgaria)', value: 'bg-BG' },
{ name: 'Burmese (Myanmar)', value: 'my-MM' },
{ name: 'Catalan (Spain)', value: 'ca-ES' },
{ name: 'Chinese, Cantonese (Traditional, Hong Kong)', value: 'yue-Hant-HK' },
{ name: 'Chinese, Mandarin (Simplified, China)', value: 'zh' },
{ name: 'Chinese, Mandarin (Simplified, Hong Kong)', value: 'zh-HK' },
{ name: 'Chinese, Mandarin (Simplified, Taiwan)', value: 'zh-TW' },
{ name: 'Croatian (Croatia)', value: 'hr-HR' },
{ name: 'Czech (Czech Republic)', value: 'cs-CZ' },
{ name: 'Danish (Denmark)', value: 'da-DK' },
{ name: 'Dutch (Belgium)', value: 'nl-BE' },
{ name: 'Dutch (Netherlands)', value: 'nl-NL' },
{ name: 'English (Australia)', value: 'en-AU' },
{ name: 'English (Canada)', value: 'en-CA' },
{ name: 'English (Ghana)', value: 'en-GH' },
{ name: 'English (India)', value: 'en-IN' },
{ name: 'English (Ireland)', value: 'en-IE' },
{ name: 'English (Kenya)', value: 'en-KE' },
{ name: 'English (New Zealand)', value: 'en-NZ' },
{ name: 'English (Nigeria)', value: 'en-NG' },
{ name: 'English (Philippines)', value: 'en-PH' },
{ name: 'English (Singapore)', value: 'en-SG' },
{ name: 'English (South Africa)', value: 'en-ZA' },
{ name: 'English (Tanzania)', value: 'en-TZ' },
{ name: 'English (United Kingdom)', value: 'en-GB' },
{ name: 'English (United States)', value: 'en-US' },
{ name: 'Estonian (Estonia)', value: 'et-EE' },
{ name: 'Filipino (Philippines)', value: 'fil-PH' },
{ name: 'Finnish (Finland)', value: 'fi-FI' },
{ name: 'French (Canada)', value: 'fr-CA' },
{ name: 'French (France)', value: 'fr-FR' },
{ name: 'Galician (Spain)', value: 'gl-ES' },
{ name: 'Georgian (Georgia)', value: 'ka-GE' },
{ name: 'German (Germany)', value: 'de-DE' },
{ name: 'Greek (Greece)', value: 'el-GR' },
{ name: 'Gujarati (India)', value: 'gu-IN' },
{ name: 'Hebrew (Israel)', value: 'he-IL' },
{ name: 'Hindi (India)', value: 'hi-IN' },
{ name: 'Hungarian (Hungary)', value: 'hu-HU' },
{ name: 'Icelandic (Iceland)', value: 'is-IS' },
{ name: 'Indonesian (Indonesia)', value: 'id-ID' },
{ name: 'Italian (Italy)', value: 'it-IT' },
{ name: 'Japanese (Japan)', value: 'ja-JP' },
{ name: 'Javanese (Indonesia)', value: 'jv-ID' },
{ name: 'Kannada (India)', value: 'kn-IN' },
{ name: 'Khmer (Cambodia)', value: 'km-KH' },
{ name: 'Korean (South Korea)', value: 'ko-KR' },
{ name: 'Lao (Laos)', value: 'lo-LA' },
{ name: 'Latvian (Latvia)', value: 'lv-LV' },
{ name: 'Lithuanian (Lithuania)', value: 'lt-LT' },
{ name: 'Macedonian (North Macedonia)', value: 'mk-MK' },
{ name: 'Malay (Malaysia)', value: 'ms-MY' },
{ name: 'Malayalam (India)', value: 'ml-IN' },
{ name: 'Marathi (India)', value: 'mr-IN' },
{ name: 'Mongolian (Mongolia)', value: 'mn-MN' },
{ name: 'Nepali (Nepal)', value: 'ne-NP' },
{ name: 'Norwegian Bokmål (Norway)', value: 'nb-NO' },
{ name: 'Persian (Iran)', value: 'fa-IR' },
{ name: 'Polish (Poland)', value: 'pl-PL' },
{ name: 'Portuguese (Brazil)', value: 'pt-BR' },
{ name: 'Portuguese (Portugal)', value: 'pt-PT' },
{ name: 'Punjabi (Gurmukhi, India)', value: 'pa-guru-IN' },
{ name: 'Romanian (Romania)', value: 'ro-RO' },
{ name: 'Russian (Russia)', value: 'ru-RU' },
{ name: 'Serbian (Serbia)', value: 'sr-RS' },
{ name: 'Sinhala (Sri Lanka)', value: 'si-LK' },
{ name: 'Slovak (Slovakia)', value: 'sk-SK' },
{ name: 'Slovenian (Slovenia)', value: 'sl-SI' },
{ name: 'Spanish (Argentina)', value: 'es-AR' },
{ name: 'Spanish (Bolivia)', value: 'es-BO' },
{ name: 'Spanish (Chile)', value: 'es-CL' },
{ name: 'Spanish (Colombia)', value: 'es-CO' },
{ name: 'Spanish (Costa Rica)', value: 'es-CR' },
{ name: 'Spanish (Dominican Republic)', value: 'es-DO' },
{ name: 'Spanish (Ecuador)', value: 'es-EC' },
{ name: 'Spanish (El Salvador)', value: 'es-SV' },
{ name: 'Spanish (Guatemala)', value: 'es-GT' },
{ name: 'Spanish (Honduras)', value: 'es-HN' },
{ name: 'Spanish (Mexico)', value: 'es-MX' },
{ name: 'Spanish (Nicaragua)', value: 'es-NI' },
{ name: 'Spanish (Panama)', value: 'es-PA' },
{ name: 'Spanish (Paraguay)', value: 'es-PY' },
{ name: 'Spanish (Peru)', value: 'es-PE' },
{ name: 'Spanish (Puerto Rico)', value: 'es-PR' },
{ name: 'Spanish (Spain)', value: 'es-ES' },
{ name: 'Spanish (United States)', value: 'es-US' },
{ name: 'Spanish (Uruguay)', value: 'es-UY' },
{ name: 'Spanish (Venezuela)', value: 'es-VE' },
{ name: 'Sundanese (Indonesia)', value: 'su-ID' },
{ name: 'Swahili (Kenya)', value: 'sw-KE' },
{ name: 'Swahili (Tanzania)', value: 'sw-TZ' },
{ name: 'Swedish (Sweden)', value: 'sv-SE' },
{ name: 'Tamil (India)', value: 'ta-IN' },
{ name: 'Tamil (Malaysia)', value: 'ta-MY' },
{ name: 'Tamil (Singapore)', value: 'ta-SG' },
{ name: 'Tamil (Sri Lanka)', value: 'ta-LK' },
{ name: 'Telugu (India)', value: 'te-IN' },
{ name: 'Thai (Thailand)', value: 'th-TH' },
{ name: 'Turkish (Turkey)', value: 'tr-TR' },
{ name: 'Ukrainian (Ukraine)', value: 'uk-UA' },
{ name: 'Urdu (India)', value: 'ur-IN' },
{ name: 'Urdu (Pakistan)', value: 'ur-PK' },
{ name: 'Uzbek (Uzbekistan)', value: 'uz-UZ' },
{ name: 'Vietnamese (Vietnam)', value: 'vi-VN' },
{ name: 'Zulu (South Africa)', value: 'zu-ZA' },
];

View File

@@ -0,0 +1,82 @@
module.exports = [
{
name: 'Arabic (Modern Standard)',
value: 'ar-MS_Telephony',
},
{
name: 'Chinese (Mandarin)',
value: 'zh-CN_Telephony',
},
{
name: 'Czech ',
value: 'cs-CZ_Telephony',
},
{
name: 'Dutch (Belgian)',
value: 'nl-BE_Telephony',
},
{
name: 'Dutch (Netherlands)',
value: 'nl-NL_Telephony',
},
{
name: 'English (all supported dialects)',
value: 'en-WW_Medical_Telephony',
},
{
name: 'English (Australian)',
value: 'en-AU_Telephony',
},
{
name: 'English (Indian)',
value: 'en-IN_Telephony',
},
{
name: 'English (United Kingdom)',
value: 'en-GB_Telephony',
},
{
name: 'English (United States)',
value: 'en-US_Telephony',
},
{
name: 'French (Canadian)',
value: 'fr-CA_Telephony',
},
{
name: 'French (France)',
value: 'fr-FR_Telephony',
},
{
name: 'German',
value: 'de-DE_Telephony',
},
{
name: 'Hindi (Indian)',
value: 'hi-IN_Telephony',
},
{
name: 'Italian',
value: 'it-IT_Telephony',
},
{
name: 'Korean',
value: 'ko-KR_Telephony',
},
{
name: 'Portuguese (Brazilian)',
value: 'pt-BR_Telephony',
},
{
name: 'Spanish (Mexican)',
value: 'es-LA_Telephony',
},
{
name: 'Spanish (Castilian)',
value: 'es-ES_Telephony',
},
{
name: 'Swedish ',
value: 'sv-SE_Telephony',
},
];

View File

@@ -0,0 +1,490 @@
module.exports = [
{
name: 'Afrikaans (South Africa)',
value: 'af-ZA',
},
{
name: 'Amharic (Ethiopia)',
value: 'am-ET',
},
{
name: 'Arabic (Algeria)',
value: 'ar-DZ',
},
{
name: 'Arabic (Bahrain)',
value: 'ar-BH',
},
{
name: 'Arabic (Egypt)',
value: 'ar-EG',
},
{
name: 'Arabic (Iraq)',
value: 'ar-IQ',
},
{
name: 'Arabic (Israel)',
value: 'ar-IL',
},
{
name: 'Arabic (Jordan)',
value: 'ar-JO',
},
{
name: 'Arabic (Kuwait)',
value: 'ar-KW',
},
{
name: 'Arabic (Lebanon)',
value: 'ar-LB',
},
{
name: 'Arabic (Libya)',
value: 'ar-LY',
},
{
name: 'Arabic (Morocco)',
value: 'ar-MA',
},
{
name: 'Arabic (Oman)',
value: 'ar-OM',
},
{
name: 'Arabic (Qatar)',
value: 'ar-QA',
},
{
name: 'Arabic (Saudi Arabia)',
value: 'ar-SA',
},
{
name: 'Arabic (Palestinian Authority)',
value: 'ar-PS',
},
{
name: 'Arabic (Syria)',
value: 'ar-SY',
},
{
name: 'Arabic (Tunisia)',
value: 'ar-TN',
},
{
name: 'Arabic (United Arab Emirates)',
value: 'ar-AE',
},
{
name: 'Arabic (Yemen)',
value: 'ar-YE',
},
{
name: 'Bulgarian (Bulgaria)',
value: 'bg-BG',
},
{
name: 'Bengali (India)',
value: 'bn-IN',
},
{
name: 'Catalan (Spain)',
value: 'ca-ES',
},
{
name: 'Chinese (Cantonese, Traditional)',
value: 'zh-HK',
},
{
name: 'Chinese (Mandarin, Simplified)',
value: 'zh-CN',
},
{
name: 'Chinese (Taiwanese Mandarin)',
value: 'zh-TW',
},
{
name: 'Croatian (Croatia)',
value: 'hr-HR',
},
{
name: 'Czech (Czech)',
value: 'cs-CZ',
},
{
name: 'Danish (Denmark)',
value: 'da-DK',
},
{
name: 'Dutch (Netherlands)',
value: 'nl-NL',
},
{
name: 'Dutch (Belgium)',
value: 'nl-BE',
},
{
name: 'English (Australia)',
value: 'en-AU',
},
{
name: 'English (Canada)',
value: 'en-CA',
},
{
name: 'English (Ghana)',
value: 'en-GH',
},
{
name: 'English (Hong Kong)',
value: 'en-HK',
},
{
name: 'English (India)',
value: 'en-IN',
},
{
name: 'English (Ireland)',
value: 'en-IE',
},
{
name: 'English (Kenya)',
value: 'en-KE',
},
{
name: 'English (New Zealand)',
value: 'en-NZ',
},
{
name: 'English (Nigeria)',
value: 'en-NG',
},
{
name: 'English (Philippines)',
value: 'en-PH',
},
{
name: 'English (Singapore)',
value: 'en-SG',
},
{
name: 'English (South Africa)',
value: 'en-ZA',
},
{
name: 'English (Tanzania)',
value: 'en-TZ',
},
{
name: 'English (United Kingdom)',
value: 'en-GB',
},
{
name: 'English (United States)',
value: 'en-US',
},
{
name: 'Estonian(Estonia)',
value: 'et-EE',
},
{
name: 'Filipino (Philippines)',
value: 'fil-PH',
},
{
name: 'Finnish (Finland)',
value: 'fi-FI',
},
{
name: 'French (Belgium)',
value: 'fr-BE',
},
{
name: 'French (Canada)',
value: 'fr-CA',
},
{
name: 'French (France)',
value: 'fr-FR',
},
{
name: 'French (Switzerland)',
value: 'fr-CH',
},
{
name: 'German (Austria)',
value: 'de-AT',
},
{
name: 'German (Switzerland)',
value: 'de-CH',
},
{
name: 'German (Germany)',
value: 'de-DE',
},
{
name: 'Greek (Greece)',
value: 'el-GR',
},
{
name: 'Gujarati (Indian)',
value: 'gu-IN',
},
{
name: 'Hebrew (Israel)',
value: 'he-IL',
},
{
name: 'Hindi (India)',
value: 'hi-IN',
},
{
name: 'Hungarian (Hungary)',
value: 'hu-HU',
},
{
name: 'Indonesian (Indonesia)',
value: 'id-ID',
},
{
name: 'Icelandic (Iceland)',
value: 'is-IS',
},
{
name: 'Irish (Ireland)',
value: 'ga-IE',
},
{
name: 'Italian (Italy)',
value: 'it-IT',
},
{
name: 'Japanese (Japan)',
value: 'ja-JP',
},
{
name: 'Javanese (Indonesia)',
value: 'jv-ID',
},
{
name: 'Kannada (India)',
value: 'kn-IN',
},
{
name: 'Khmer (Cambodia)',
value: 'km-KH',
},
{
name: 'Korean (Korea)',
value: 'ko-KR',
},
{
name: 'Latvian (Latvia)',
value: 'lv-LV',
},
{
name: 'Lao (Laos)',
value: 'lo-LA',
},
{
name: 'Lithuanian (Lithuania)',
value: 'lt-LT',
},
{
name: 'Malay (Malaysia)',
value: 'ms-MY',
},
{
name: 'Macedonian (North Macedonia)',
value: 'mk-MK',
},
{
name: 'Maltese (Malta)',
value: 'mt-MT',
},
{
name: 'Marathi (India)',
value: 'mr-IN',
},
{
name: 'Burmese (Myanmar)',
value: 'my-MM',
},
{
name: 'Norwegian (Bokmål, Norway)',
value: 'nb-NO',
},
{
name: 'Persian (Iran)',
value: 'fa-IR',
},
{
name: 'Polish (Poland)',
value: 'pl-PL',
},
{
name: 'Portuguese (Brazil)',
value: 'pt-BR',
},
{
name: 'Portuguese (Portugal)',
value: 'pt-PT',
},
{
name: 'Romanian (Romania)',
value: 'ro-RO',
},
{
name: 'Russian (Russia)',
value: 'ru-RU',
},
{
name: 'Slovak (Slovakia)',
value: 'sk-SK',
},
{
name: 'Slovenian (Slovenia)',
value: 'sl-SI',
},
{
name: 'Spanish (Argentina)',
value: 'es-AR',
},
{
name: 'Spanish (Bolivia)',
value: 'es-BO',
},
{
name: 'Spanish (Chile)',
value: 'es-CL',
},
{
name: 'Spanish (Colombia)',
value: 'es-CO',
},
{
name: 'Spanish (Costa Rica)',
value: 'es-CR',
},
{
name: 'Spanish (Cuba)',
value: 'es-CU',
},
{
name: 'Spanish (Dominican Republic)',
value: 'es-DO',
},
{
name: 'Spanish (Ecuador)',
value: 'es-EC',
},
{
name: 'Spanish (El Salvador)',
value: 'es-SV',
},
{
name: 'Spanish (Equatorial Guinea)',
value: 'es-GQ',
},
{
name: 'Spanish (Guatemala)',
value: 'es-GT',
},
{
name: 'Spanish (Honduras)',
value: 'es-HN',
},
{
name: 'Spanish (Mexico)',
value: 'es-MX',
},
{
name: 'Spanish (Nicaragua)',
value: 'es-NI',
},
{
name: 'Spanish (Panama)',
value: 'es-PA',
},
{
name: 'Spanish (Paraguay)',
value: 'es-PY',
},
{
name: 'Spanish (Peru)',
value: 'es-PE',
},
{
name: 'Spanish (Puerto Rico)',
value: 'es-PR',
},
{
name: 'Spanish (Spain)',
value: 'es-ES',
},
{
name: 'Spanish (Uruguay)',
value: 'es-UY',
},
{
name: 'Spanish (USA)',
value: 'es-US',
},
{
name: 'Spanish (Venezuela)',
value: 'es-VE',
},
{
name: 'Swahili (Kenya)',
value: 'sw-KE',
},
{
name: 'Swahili (Tanzania)',
value: 'sw-TZ',
},
{
name: 'Sinhala (Sri Lanka)',
value: 'si-LK',
},
{
name: 'Swedish (Sweden)',
value: 'sv-SE',
},
{
name: 'Serbian (Serbia)',
value: 'sr-RS',
},
{
name: 'Tamil (India)',
value: 'ta-IN',
},
{
name: 'Telugu (India)',
value: 'te-IN',
},
{
name: 'Thai (Thailand)',
value: 'th-TH',
},
{
name: 'Turkish (Turkey)',
value: 'tr-TR',
},
{
name: 'Ukrainian (Ukraine)',
value: 'uk-UA',
},
{
name: 'Uzbek (Uzbekistan)',
value: 'uz-UZ',
},
{
name: 'Zulu (South Africa)',
value: 'zu-ZA',
},
{
name: 'Vietnamese (Vietnam)',
value: 'vi-VN',
},
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'Whisper', value: 'whisper-1' },
{ name: 'GPT 4o Mini Transcribe', value: 'gpt-4o-mini-transcribe' },
{ name: 'GLT 4o Transcribe', value: 'gpt-4o-transcribe' },
];

View File

@@ -0,0 +1,207 @@
module.exports = [
{
name: 'Arabic (Worldwide)',
value: 'ar-WW',
valueMix: 'ara-XWW',
},
{
name: 'Catalan (Spain)',
value: 'ca-ES',
valueMix: 'cat-ESP',
},
{
name: 'Croatian (Croatia)',
value: 'hr-HR',
valueMix: 'hrv-HRV',
},
{
name: 'Czech (Czech Republic)',
value: 'cs-CZ',
valueMix: 'ces-CZE',
},
{
name: 'Danish (Denmark)',
value: 'da-DK',
valueMix: 'dan-DNK',
},
{
name: 'Dutch (Netherlands)',
value: 'nl-NL',
valueMix: 'nld-NLD',
},
{
name: 'English (Australia)',
value: 'en-AU',
valueMix: 'eng-AUS',
},
{
name: 'English (United States)',
value: 'en-US',
valueMix: 'eng-USA',
},
{
name: 'English (India)',
value: 'en-IN',
valueMix: 'eng-IND',
},
{
name: 'English (United Kingdom)',
value: 'en-GB',
valueMix: 'eng-GBR',
},
{
name: 'Finnish (Finland)',
value: 'fi-FI',
valueMix: 'fin-FIN',
},
{
name: 'French (Canada)',
value: 'fr-CA',
valueMix: 'fra-CAN',
},
{
name: 'French (France)',
value: 'fr-FR',
valueMix: 'fra-FRA',
},
{
name: 'German (Germany)',
value: 'de-DE',
valueMix: 'deu-DEU',
},
{
name: 'Greek (Greece)',
value: 'el-GR',
valueMix: 'ell-GRC',
},
{
name: 'Hebrew (Israel)',
value: 'he-IL',
valueMix: 'heb-ISR',
},
{
name: 'Hindi (India)',
value: 'hi-IN',
valueMix: 'hin-IND',
},
{
name: 'Hungarian (Hungary)',
value: 'hu-HU',
valueMix: 'hun-HUN',
},
{
name: 'Indonesian (Indonesia)',
value: 'id-ID',
valueMix: 'ind-IDN',
},
{
name: 'Italian (Italy)',
value: 'it-IT',
valueMix: 'ita-ITA',
},
{
name: 'Japanese (Japan)',
value: 'ja-JP',
valueMix: 'jpn-JPN',
},
{
name: 'Korean (South Korea)',
value: 'ko-KR',
valueMix: 'kor-KOR',
},
{
name: 'Malay (Malaysia)',
value: 'ms-MY',
valueMix: 'zlm-MYS',
},
{
name: 'Norwegian (Norway)',
value: 'no-NO',
valueMix: 'nor-NOR',
},
{
name: 'Polish (Poland)',
value: 'pl-PL',
valueMix: 'pol-POL',
},
{
name: 'Portuguese (Brazil)',
value: 'pt-BR',
valueMix: 'por-BRA',
},
{
name: 'Portuguese (Portugal)',
value: 'pt-PT',
valueMix: 'por-PRT',
},
{
name: 'Romanian (Romania)',
value: 'ro-RO',
valueMix: 'ron-ROU',
},
{
name: 'Russian (Russia)',
value: 'ru-RU',
valueMix: 'rus-RUS',
},
{
name: 'Shanghainese (China)',
value: 'zh-WU',
valueMix: 'wuu-CHN',
},
{
name: 'Mandarin (China)',
value: 'zh-CN',
valueMix: 'cmn-CHN',
},
{
name: 'Slovak (Slovakia)',
value: 'sk-SK',
valueMix: 'slk-SVK',
},
{
name: 'Spanish (Spain)',
value: 'es-ES',
valueMix: 'spa-ESP',
},
{
name: 'Spanish (Latin America)',
value: 'es-US',
valueMix: 'spa-XLA',
},
{
name: 'Swedish (Sweden)',
value: 'sv-SE',
valueMix: 'swe-SWE',
},
{
name: 'Thai (Thailand)',
value: 'th-TH',
valueMix: 'tha-THA',
},
{
name: 'Cantonese (Hong Kong)',
value: 'cn-HK',
valueMix: 'yue-CHS',
},
{
name: 'Mandarin (Taiwan)',
value: 'zh-TW',
valueMix: 'cmn-TWN',
},
{
name: 'Turkish (Turkey)',
value: 'tr-TR',
valueMix: 'tur-TUR',
},
{
name: 'Ukrainian (Ukraine)',
value: 'uk-UA',
valueMix: 'ukr-UKR',
},
{
name: 'Vietnamese (Vietnam)',
value: 'vi-VN',
valueMix: 'vie-VNM',
},
];

View File

@@ -0,0 +1,58 @@
module.exports = [
{
name: 'Arabic',
value: 'ar-AR',
},
{
name: 'English',
value: 'en-US',
},
{
name: 'English - GB',
value: 'en-GB',
},
{
name: 'Spanish - US',
value: 'es-US',
},
{
name: 'Spanish',
value: 'es-ES',
},
{
name: 'German',
value: 'de-DE',
},
{
name: 'French',
value: 'fr-FR',
},
{
name: 'Hindi',
value: 'hi-IN',
},
{
name: 'Russian',
value: 'ru-RU',
},
{
name: 'Korean',
value: 'ko-KR',
},
{
name: 'Brazilian-Portuguese',
value: 'pt-BR',
},
{
name: 'Japanese',
value: 'ja-JP',
},
{
name: 'Italian',
value: 'it-IT',
},
{
name: 'Mandarin',
value: 'zh-CN',
},
];

View File

@@ -0,0 +1,59 @@
module.exports = [
{ name: 'Afrikaans', value: 'af' },
{ name: 'Arabic', value: 'ar' },
{ name: 'Azerbaijani', value: 'az' },
{ name: 'Belarusian', value: 'be' },
{ name: 'Bulgarian', value: 'bg' },
{ name: 'Bosnian', value: 'bs' },
{ name: 'Catalan', value: 'ca' },
{ name: 'Czech', value: 'cs' },
{ name: 'Welsh', value: 'cy' },
{ name: 'Danish', value: 'da' },
{ name: 'German', value: 'de' },
{ name: 'Greek', value: 'el' },
{ name: 'English', value: 'en' },
{ name: 'Spanish', value: 'es' },
{ name: 'Estonian', value: 'et' },
{ name: 'Persian', value: 'fa' },
{ name: 'Finnish', value: 'fi' },
{ name: 'French', value: 'fr' },
{ name: 'Galician', value: 'gl' },
{ name: 'Hebrew', value: 'he' },
{ name: 'Hindi', value: 'hi' },
{ name: 'Croatian', value: 'hr' },
{ name: 'Hungarian', value: 'hu' },
{ name: 'Armenian', value: 'hy' },
{ name: 'Indonesian', value: 'id' },
{ name: 'Icelandic', value: 'is' },
{ name: 'Italian', value: 'it' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Kazakh', value: 'kk' },
{ name: 'Kannada', value: 'kn' },
{ name: 'Korean', value: 'ko' },
{ name: 'Lithuanian', value: 'lt' },
{ name: 'Latvian', value: 'lv' },
{ name: 'Maori', value: 'mi' },
{ name: 'Macedonian', value: 'mk' },
{ name: 'Marathi', value: 'mr' },
{ name: 'Malay', value: 'ms' },
{ name: 'Nepali', value: 'ne' },
{ name: 'Dutch', value: 'nl' },
{ name: 'Norwegian', value: 'no' },
{ name: 'Polish', value: 'pl' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Romanian', value: 'ro' },
{ name: 'Russian', value: 'ru' },
{ name: 'Slovak', value: 'sk' },
{ name: 'Slovenian', value: 'sl' },
{ name: 'Serbian', value: 'sr' },
{ name: 'Swedish', value: 'sv' },
{ name: 'Swahili', value: 'sw' },
{ name: 'Tamil', value: 'ta' },
{ name: 'Thai', value: 'th' },
{ name: 'Tagalog', value: 'tl' },
{ name: 'Turkish', value: 'tr' },
{ name: 'Ukrainian', value: 'uk' },
{ name: 'Urdu', value: 'ur' },
{ name: 'Vietnamese', value: 'vi' },
{ name: 'Chinese', value: 'zh' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{
name: 'English (United States)',
value: 'en-US',
},
];

View 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',
},
];

View File

@@ -0,0 +1,14 @@
module.exports = [
{ name: 'US English', value: 'en-US' },
{ name: 'British English', value: 'en-GB' },
{ name: 'LATAM Spanish', value: 'en-USes-419' },
{ name: 'Spanish', value: 'es' },
{ name: 'Catalan', value: 'ca-ES', version: 'v2' },
{ name: 'Brazilian Portuguese', value: 'pt-BR' },
{ name: 'French', value: 'fr', version: 'v1' },
{ name: 'Canadian French', value: 'fr-CA', version: 'v1' },
{ name: 'German', value: 'de', version: 'v1' },
{ name: 'Italian', value: 'it', version: 'v1' },
{ name: 'Turkish', value: 'tr', version: 'v1' },
{ name: 'Japanese', value: 'ja', version: 'v1' },
];

View 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' },
];

View File

@@ -0,0 +1,213 @@
module.exports = [
{
value: 'arb',
name: 'Arabic',
voices: [{ value: 'Zeina', name: 'Zeina (Female)' }],
},
{
value: 'cmn-CN',
name: 'Chinese, Mandarin',
voices: [{ value: 'Zhiyu', name: 'Zhiyu (Female)' }],
},
{
value: 'da-DK',
name: 'Danish',
voices: [
{ value: 'Naja', name: 'Naja (Female)' },
{ value: 'Mads', name: 'Mads (Male)' },
],
},
{
value: 'nl-NL',
name: 'Dutch',
voices: [
{ value: 'Lotte', name: 'Lotte (Female)' },
{ value: 'Ruben', name: 'Ruben (Male)' },
],
},
{
value: 'en-AU',
name: 'English (Australian)',
voices: [
{ value: 'Nicole', name: 'Nicole (Female)' },
{ value: 'Russell', name: 'Russell (Male)' },
],
},
{
value: 'en-GB',
name: 'English (British)',
voices: [
{ value: 'Amy', name: 'Amy (Female)' },
{ value: 'Emma', name: 'Emma (Female)' },
{ value: 'Brian', name: 'Brian (Male)' },
],
},
{
value: 'en-IN',
name: 'English (Indian)',
voices: [
{ value: 'Aditi', name: 'Aditi (Female)' },
{ value: 'Raveena', name: 'Raveena (Female)' },
],
},
{
value: 'en-US',
name: 'English (US)',
voices: [
{ value: 'Joanna', name: 'Joanna (Female)' },
{ value: 'Kendra', name: 'Kendra (Female)' },
{ value: 'Kimberly', name: 'Kimberly (Female)' },
{ value: 'Ivy', name: 'Ivy (Female child)' },
{ value: 'Salli', name: 'Salli (Female)' },
{ value: 'Joey', name: 'Joey (Male)' },
{ value: 'Matthew', name: 'Matthew (Male)' },
{ value: 'Justin', name: 'Justin (Male child)' },
],
},
{
value: 'en-GB-WLS',
name: 'English (Welsh)',
voices: [{ value: 'Geraint', name: 'Geraint (Male)' }],
},
{
value: 'fr-FR',
name: 'French',
voices: [
{ value: 'Celine', name: 'Céline (Female)' },
{ value: 'Lea', name: 'Léa (Female)' },
{ value: 'Mathieu', name: 'Mathieu (Male)' },
],
},
{
value: 'fr-CA',
name: 'French (Canadian)',
voices: [{ value: 'Chantal', name: 'Chantal (Female)' }],
},
{
value: 'de-DE',
name: 'German',
voices: [
{ value: 'Marlene', name: 'Marlene (Female)' },
{ value: 'Vicki', name: 'Vicki (Female)' },
{ value: 'Hans', name: 'Hans (Male)' },
],
},
{
value: 'hi-IN',
name: 'Hindi',
voices: [{ value: 'Aditi', name: 'Aditi (Female)' }],
},
{
value: 'is-IS',
name: 'Icelandic',
voices: [
{ value: 'Dora', name: 'Dóra (Female)' },
{ value: 'Karl', name: 'Karl (Male)' },
],
},
{
value: 'it-IT',
name: 'Italian',
voices: [
{ value: 'Carla', name: 'Carla (Female)' },
{ value: 'Bianca', name: 'Bianca (Female)' },
{ value: 'Giorgio', name: 'Giorgio (Male)' },
],
},
{
value: 'ja-JP',
name: 'Japanese',
voices: [
{ value: 'Mizuki', name: 'Mizuki (Female)' },
{ value: 'Takumi', name: 'Takumi (Male)' },
],
},
{
value: 'ko-KR',
name: 'Korean',
voices: [{ value: 'Seoyeon', name: 'Seoyeon (Female)' }],
},
{
value: 'nb-NO',
name: 'Norwegian',
voices: [{ value: 'Liv', name: 'Liv (Female)' }],
},
{
value: 'pl-PL',
name: 'Polish',
voices: [
{ value: 'Ewa', name: 'Ewa (Female)' },
{ value: 'Maja', name: 'Maja (Female)' },
{ value: 'Jacek', name: 'Jacek (Male)' },
{ value: 'Jan', name: 'Jan (Male)' },
],
},
{
value: 'pt-BR',
name: 'Portuguese (Brazilian)',
voices: [
{ value: 'Camila', name: 'Camila (Female)' },
{ value: 'Vitoria', name: 'Vitória (Female)' },
{ value: 'Ricardo', name: 'Ricardo (Male)' },
],
},
{
value: 'pt-PT',
name: 'Portuguese (European)',
voices: [
{ value: 'Ines', name: 'Inês (Female)' },
{ value: 'Cristiano', name: 'Cristiano (Male)' },
],
},
{
value: 'ro-RO',
name: 'Romanian',
voices: [{ value: 'Carmen', name: 'Carmen (Female)' }],
},
{
value: 'ru-RU',
name: 'Russian',
voices: [
{ value: 'Tatyana', name: 'Tatyana (Female)' },
{ value: 'Maxim', name: 'Maxim (Male)' },
],
},
{
value: 'es-ES',
name: 'Spanish (European)',
voices: [
{ value: 'Conchita', name: 'Conchita (Female)' },
{ value: 'Lucia', name: 'Lucia (Female)' },
{ value: 'Enrique', name: 'Enrique (Male)' },
],
},
{
value: 'es-MX',
name: 'Spanish (Mexican)',
voices: [{ value: 'Mia', name: 'Mia (Female)' }],
},
{
value: 'es-US',
name: 'Spanish (US)',
voices: [
{ value: 'Lupe', name: 'Lupe (Female)' },
{ value: 'Penelope', name: 'Penélope (Female)' },
{ value: 'Miguel', name: 'Miguel (Male)' },
],
},
{
value: 'sv-SE',
name: 'Swedish',
voices: [{ value: 'Astrid', name: 'Astrid (Female)' }],
},
{
value: 'tr-TR',
name: 'Turkish',
voices: [{ value: 'Filiz', name: 'Filiz (Female)' }],
},
{
value: 'cy-GB',
name: 'Welsh',
voices: [{ value: 'Gwyneth', name: 'Gwyneth (Female)' }],
},
];

View 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.',
},
],
},
];

View 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;

View File

@@ -0,0 +1,192 @@
module.exports = [
{
value: 'ar',
name: 'Arabic',
voices: [
{
value: 'pNInz6obpgDQGcFmaJgB',
name: 'Adam - american, deep, middle aged, male, narration',
},
{
value: 'ErXwobaYiN019PkySvjV',
name: 'Antoni - american, well-rounded, young, male, narration',
},
{
value: 'VR6AewLTigWG4xSOukaG',
name: 'Arnold - american, crisp, middle aged, male, narration',
},
{
value: 'EXAVITQu4vr4xnSDxMaL',
name: 'Bella - american, soft, young, female, narration',
},
{
value: 'N2lVS1w4EtoT3dr4eOWO',
name: 'Callum - american, hoarse, middle aged, male, video games',
},
{
value: 'IKne3meq5aSn9XLyUdCD',
name: 'Charlie - australian, casual, middle aged, male, conversational',
},
{
value: 'XB0fDUnXU5powFXDhCwa',
name: 'Charlotte - english-swedish, seductive, middle aged, female, video games',
},
{
value: '2EiwWnXFnvU5JabPnv8n',
name: 'Clyde - american, war veteran, middle aged, male, video games',
},
{
value: 'onwK4e9ZLuTAKqWW03F9',
name: 'Daniel - british, deep, middle aged, male, news presenter',
},
{
value: 'CYw3kZ02Hs0563khs1Fj',
name: 'Dave - british-essex, conversational, young, male, video games',
},
{
value: 'AZnzlk1XvdvUeBnXmlld',
name: 'Domi - american, strong, young, female, narration',
},
{
value: 'ThT5KcBeYPX3keUQqHPh',
name: "Dorothy - british, pleasant, young, female, children's stories",
},
{
value: 'MF3mGyEYCl7XYWbV9V6O',
name: 'Elli - american, emotional, young, female, narration',
},
{
value: 'LcfcDJNUP1GQjkzn1xUU',
name: 'Emily - american, calm, young, female, meditation',
},
{
value: 'g5CIjZEefAph4nQFvHAz',
name: 'Ethan - american, undefined, young, male, ASMR',
},
{
value: 'D38z5RcWu1voky8WS1ja',
name: 'Fin - irish, sailor, old, male, video games',
},
{
value: 'jsCqWAovK2LkecY7zXl4',
name: 'Freya - american, undefined, young, female, undefined',
},
{
value: 'jBpfuIE2acCO8z3wKNLl',
name: 'Gigi - american, childlish, young, female, animation',
},
{
value: 'zcAOhNBS3c14rBihAFp1',
name: 'Giovanni - english-italian, foreigner, young, male, audiobook',
},
{
value: 'z9fAnlkpzviPz146aGWa',
name: 'Glinda - american, witch, middle aged, female, video games',
},
{
value: 'oWAxZDx7w5VEj9dCyTzz',
name: 'Grace - american-southern, undefined, young, female, audiobook ',
},
{
value: 'SOYHLrjzK2X1ezoPC6cr',
name: 'Harry - american, anxious, young, male, video games',
},
{
value: 'ZQe5CZNOzWyzPSCn5a3c',
name: 'James - australian, calm , old, male, news',
},
{
value: 'bVMeCyTHy58xNoL34h3p',
name: 'Jeremy - american-irish, excited, young, male, narration',
},
{
value: 't0jbNlBVZ17f02VDIeMI',
name: 'Jessie - american, raspy , old, male, video games',
},
{
value: 'Zlb1dXrM653N07WRdFW3',
name: 'Joseph - british, undefined, middle aged, male, news',
},
{
value: 'TxGEqnHWrfWFTfGW9XjX',
name: 'Josh - american, deep, young, male, narration',
},
{
value: 'TX3LPaxmHKxFdv7VOQHJ',
name: 'Liam - american, undefined, young, male, narration',
},
{
value: 'XrExE9yKIg1WjnnlVkGX',
name: 'Matilda - american, warm, young, female, audiobook',
},
{
value: 'Yko7PKHZNXotIFUBG7I9',
name: 'Matthew - british, undefined, middle aged, male, audiobook',
},
{
value: 'flq6f7yk4E4fJM5XTYuZ',
name: 'Michael - american, undefined, old, male, audiobook',
},
{
value: 'zrHiDhphv9ZnVXBqCLjz',
name: 'Mimi - english-swedish, childish, young, female, animation',
},
{
value: 'piTKgcLEGmPE4e6mEKli',
name: 'Nicole - american, whisper, young, female, audiobook',
},
{
value: 'ODq5zmih8GrVes37Dizd',
name: 'Patrick - american, shouty, middle aged, male, video games',
},
{
value: '21m00Tcm4TlvDq8ikWAM',
name: 'Rachel - american, calm, young, female, narration',
},
{
value: 'wViXBPUzp2ZZixB1xQuM',
name: 'Ryan - american, soldier, middle aged, male, audiobook',
},
{
value: 'yoZ06aMxZJJ28mfd3POQ',
name: 'Sam - american, raspy, young, male, narration',
},
{
value: 'pMsXgVXv3BLzUgSXRplE',
name: 'Serena - american, pleasant, middle aged, female, interactive',
},
{
value: 'GBv7mTt0atIp3Br8iCZE',
name: 'Thomas - american, calm, young, male, meditation',
},
],
},
{ value: 'bg', name: 'Bulgarian', voices: [] },
{ value: 'zh', name: 'Chinese', voices: [] },
{ value: 'hr', name: 'Croatian', voices: [] },
{ value: 'cs', name: 'Czech', voices: [] },
{ value: 'da', name: 'Danish', voices: [] },
{ value: 'nl', name: 'Dutch', voices: [] },
{ value: 'en', name: 'English', voices: [] },
{ value: 'fil', name: 'Filipino', voices: [] },
{ value: 'fi', name: 'Finnish', voices: [] },
{ value: 'fr', name: 'French', voices: [] },
{ value: 'de', name: 'German', voices: [] },
{ value: 'el', name: 'Greek', voices: [] },
{ value: 'hi', name: 'Hindi', voices: [] },
{ value: 'id', name: 'Indonesian', voices: [] },
{ value: 'it', name: 'Italian', voices: [] },
{ value: 'ja', name: 'Japanese', voices: [] },
{ value: 'ko', name: 'Korean', voices: [] },
{ value: 'ms', name: 'Malay', voices: [] },
{ value: 'pl', name: 'Polish', voices: [] },
{ value: 'pt', name: 'Portuguese', voices: [] },
{ value: 'ro', name: 'Romanian', voices: [] },
{ value: 'ru', name: 'Russian', voices: [] },
{ value: 'sk', name: 'Slovak', voices: [] },
{ value: 'es', name: 'Spanish', voices: [] },
{ value: 'sv', name: 'Swedish', voices: [] },
{ value: 'ta', name: 'Tamil', voices: [] },
{ value: 'tr', name: 'Turkish', voices: [] },
{ value: 'uk', name: 'Ukrainian', voices: [] },
];

View File

@@ -0,0 +1,796 @@
module.exports = [
{
value: 'ar-XA',
name: 'Arabic',
voices: [
{ value: 'ar-XA-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ar-XA-Standard-B', name: 'Standard-B (Male)' },
{ value: 'ar-XA-Standard-C', name: 'Standard-C (Male)' },
{ value: 'ar-XA-Standard-D', name: 'Standard-D (Female)' },
{ value: 'ar-XA-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ar-XA-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'ar-XA-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'ar-XA-Wavenet-D', name: 'Wavenet-D (Female)' },
],
},
{
value: 'af-ZA',
name: 'Afrikaans (South Africa)',
voices: [{ value: 'af-ZA-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'bn-IN',
name: 'Bengali (India)',
voices: [
{ value: 'bn-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'bn-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'bn-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'bn-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
],
},
{
value: 'bg-BG',
name: 'Bulgarian (Bulgaria)',
voices: [{ value: 'bg-BG-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'ca-ES',
name: 'Catalan (Spain)',
voices: [{ value: 'ca-ES-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'cs-CZ',
name: 'Czech (Czech Republic)',
voices: [
{ value: 'cs-CZ-Standard-A', name: 'Standard-A (Female)' },
{ value: 'cs-CZ-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'da-DK',
name: 'Danish (Denmark)',
voices: [
{ value: 'da-DK-Standard-A', name: 'Standard-A (Female)' },
{ value: 'da-DK-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'da-DK-Neural2-D', name: 'Neural2-D (Female)' },
{ value: 'da-DK-Neural2-F', name: 'Neural2-F (Male)' },
{ value: 'da-DK-Standard-C', name: 'Standard-C (Male)' },
{ value: 'da-DK-Standard-D', name: 'Standard-D (Female)' },
{ value: 'da-DK-Standard-E', name: 'Standard-E (Female)' },
{ value: 'da-DK-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'da-DK-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'da-DK-Wavenet-E', name: 'Wavenet-E (Female)' },
],
},
{
value: 'eu-ES',
name: 'Basque (Spain)',
voices: [{ value: 'eu-ES-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'nl-NL',
name: 'Dutch (Netherlands)',
voices: [
{ value: 'nl-NL-Standard-A', name: 'Standard-A (Female)' },
{ value: 'nl-NL-Standard-B', name: 'Standard-B (Male)' },
{ value: 'nl-NL-Standard-C', name: 'Standard-C (Male)' },
{ value: 'nl-NL-Standard-D', name: 'Standard-D (Female)' },
{ value: 'nl-NL-Standard-E', name: 'Standard-E (Female)' },
{ value: 'nl-NL-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'nl-NL-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'nl-NL-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'nl-NL-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'nl-NL-Wavenet-E', name: 'Wavenet-E (Female)' },
],
},
{
value: 'en-AU',
name: 'English (Australia)',
voices: [
{ value: 'en-AU-Standard-A', name: 'Standard-A (Female)' },
{ value: 'en-AU-Standard-B', name: 'Standard-B (Male)' },
{ value: 'en-AU-Standard-C', name: 'Standard-C (Female)' },
{ value: 'en-AU-Standard-D', name: 'Standard-D (Male)' },
{ value: 'en-AU-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'en-AU-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'en-AU-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'en-AU-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'en-AU-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'en-AU-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'en-AU-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'en-AU-Neural2-D', name: 'Neural2-D (Male)' },
{ value: 'en-AU-Polyglot-1', name: 'Polyglot-1 (Male)' },
{ value: 'en-AU-News-E', name: 'News-E (Female)' },
{ value: 'en-AU-News-F', name: 'News-F (Female)' },
{ value: 'en-AU-News-G', name: 'News-G (Male)' },
],
},
{
value: 'en-IN',
name: 'English (India)',
voices: [
{ value: 'en-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'en-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'en-IN-Standard-C', name: 'Standard-C (Male)' },
{ value: 'en-IN-Standard-D', name: 'Standard-D (Female)' },
{ value: 'en-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'en-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'en-IN-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'en-IN-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'en-IN-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'en-IN-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'en-IN-Neural2-C', name: 'Neural2-C (Male)' },
{ value: 'en-IN-Neural2-D', name: 'Neural2-D (Female)' },
],
},
{
value: 'en-GB',
name: 'English (UK)',
voices: [
{ value: 'en-GB-Standard-A', name: 'Standard-A (Female)' },
{ value: 'en-GB-Standard-B', name: 'Standard-B (Male)' },
{ value: 'en-GB-Standard-C', name: 'Standard-C (Female)' },
{ value: 'en-GB-Standard-D', name: 'Standard-D (Male)' },
{ value: 'en-GB-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'en-GB-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'en-GB-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'en-GB-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'en-GB-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'en-GB-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'en-GB-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'en-GB-Neural2-D', name: 'Neural2-D (Male)' },
{ value: 'en-GB-Neural2-F', name: 'Neural2-F (Female)' },
{ value: 'en-GB-News-G', name: 'News-G (Female)' },
{ value: 'en-GB-News-H', name: 'News-H (Female)' },
{ value: 'en-GB-News-I', name: 'News-I (Female)' },
{ value: 'en-GB-News-J', name: 'News-J (Male)' },
{ value: 'en-GB-News-K', name: 'News-K (Male)' },
{ value: 'en-GB-News-L', name: 'News-L (Male)' },
{ value: 'en-GB-News-M', name: 'News-M (Male)' },
{ value: 'en-GB-Studio-B', name: 'Studio-B (Male)' },
{ value: 'en-GB-Studio-C', name: 'Studio-C (Female)' },
{ value: 'en-GB-Wavenet-F', name: 'Wavenet-F (Female)' },
{ value: 'en-GB-Standard-F', name: 'Standard-F (Female)' },
],
},
{
value: 'en-US',
name: 'English (US)',
voices: [
{ value: 'en-US-Standard-B', name: 'Standard-B (Male)' },
{ value: 'en-US-Standard-C', name: 'Standard-C (Female)' },
{ value: 'en-US-Standard-D', name: 'Standard-D (Male)' },
{ value: 'en-US-Standard-E', name: 'Standard-E (Female)' },
{ value: 'en-US-Wavenet-A', name: 'Wavenet-A (Male)' },
{ value: 'en-US-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'en-US-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'en-US-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'en-US-Wavenet-E', name: 'Wavenet-E (Female)' },
{ value: 'en-US-Wavenet-F', name: 'Wavenet-F (Female)' },
{ value: 'en-US-Neural2-A', name: 'Neural2-A (Male)' },
{ value: 'en-US-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'en-US-Neural2-D', name: 'Neural2-D (Male)' },
{ value: 'en-US-Neural2-E', name: 'Neural2-E (Female)' },
{ value: 'en-US-Neural2-F', name: 'Neural2-F (Female)' },
{ value: 'en-US-Neural2-G', name: 'Neural2-G (Female)' },
{ value: 'en-US-Neural2-H', name: 'Neural2-H (Female)' },
{ value: 'en-US-Neural2-I', name: 'Neural2-I (Male)' },
{ value: 'en-US-Neural2-J', name: 'Neural2-J (Male)' },
{ value: 'en-US-Studio-M', name: 'Studio-M (Male)' },
{ value: 'en-US-Studio-O', name: 'Studio-M (Female)' },
{ value: 'en-US-Polyglot-1', name: 'Polyglot-1 (Male)' },
{ value: 'en-US-News-K', name: 'News-K (Female)' },
{ value: 'en-US-News-L', name: 'News-L (Female)' },
{ value: 'en-US-News-M', name: 'News-M (Male)' },
{ value: 'en-US-News-N', name: 'News-N (Male)' },
{ value: 'en-US-Standard-A', name: 'Standard-A (Male)' },
{ value: 'en-US-Standard-F', name: 'Standard-F (Female)' },
{ value: 'en-US-Standard-G', name: 'Standard-G (Female)' },
{ value: 'en-US-Standard-H', name: 'Standard-H (Female)' },
{ value: 'en-US-Standard-I', name: 'Standard-I (Male)' },
{ value: 'en-US-Standard-J', name: 'Standard-J (Male)' },
{ value: 'en-US-Wavenet-G', name: 'Wavenet-G (Female)' },
{ value: 'en-US-Wavenet-H', name: 'Wavenet-H (Female)' },
{ value: 'en-US-Wavenet-I', name: 'Wavenet-I (Male)' },
{ value: 'en-US-Wavenet-J', name: 'Wavenet-J (Male)' },
],
},
{
value: 'fil-PH',
name: 'Filipino (Philippines)',
voices: [
{ value: 'fil-PH-Standard-A', name: 'Standard-A (Female)' },
{ value: 'fil-PH-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'fil-ph-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'fil-ph-Neural2-D', name: 'Neural2-A (Male)' },
{ value: 'fil-PH-Standard-B', name: 'Standard-B (Female)' },
{ value: 'fil-PH-Standard-C', name: 'Standard-C (Male)' },
{ value: 'fil-PH-Standard-D', name: 'Standard-D (Male)' },
{ value: 'fil-PH-Wavenet-B', name: 'Wavenet-B (Female)' },
{ value: 'fil-PH-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'fil-PH-Wavenet-D', name: 'Wavenet-D (Male)' },
],
},
{
value: 'fi-FI',
name: 'Finnish (Finland)',
voices: [
{ value: 'fi-FI-Standard-A', name: 'Standard-A (Female)' },
{ value: 'fi-FI-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'fr-CA',
name: 'French (Canada)',
voices: [
{ value: 'fr-CA-Standard-A', name: 'Standard-A (Female)' },
{ value: 'fr-CA-Standard-B', name: 'Standard-B (Male)' },
{ value: 'fr-CA-Standard-C', name: 'Standard-C (Female)' },
{ value: 'fr-CA-Standard-D', name: 'Standard-D (Male)' },
{ value: 'fr-CA-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'fr-CA-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'fr-CA-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'fr-CA-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'fr-CA-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'fr-CA-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'fr-CA-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'fr-CA-Neural2-D', name: 'Neural2-D (Male)' },
],
},
{
value: 'fr-FR',
name: 'French (France)',
voices: [
{ value: 'fr-FR-Standard-A', name: 'Standard-A (Female)' },
{ value: 'fr-FR-Standard-B', name: 'Standard-B (Male)' },
{ value: 'fr-FR-Standard-C', name: 'Standard-C (Female)' },
{ value: 'fr-FR-Standard-D', name: 'Standard-D (Male)' },
{ value: 'fr-FR-Standard-E', name: 'Standard-E (Female)' },
{ value: 'fr-FR-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'fr-FR-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'fr-FR-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'fr-FR-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'fr-FR-Wavenet-E', name: 'Wavenet-E (Female)' },
{ value: 'fr-FR-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'fr-FR-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'fr-FR-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'fr-FR-Neural2-D', name: 'Neural2-D (Male)' },
{ value: 'fr-FR-Neural2-E', name: 'Neural2-E (Female)' },
{ value: 'fr-FR-Polyglot-1', name: 'Polyglot-1 (Male)' },
{ value: 'fr-FR-Studio-A', name: 'Studio-A (Female)' },
{ value: 'fr-FR-Studio-D', name: 'Studio-D (Male)' },
],
},
{
value: 'de-DE',
name: 'German (Germany)',
voices: [
{ value: 'de-DE-Standard-A', name: 'Standard-A (Female)' },
{ value: 'de-DE-Standard-B', name: 'Standard-B (Male)' },
{ value: 'de-DE-Standard-E', name: 'Standard-E (Male)' },
{ value: 'de-DE-Standard-F', name: 'Standard-F (Female)' },
{ value: 'de-DE-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'de-DE-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'de-DE-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'de-DE-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'de-DE-Wavenet-E', name: 'Wavenet-E (Male)' },
{ value: 'de-DE-Wavenet-F', name: 'Wavenet-F (Female)' },
{ value: 'de-DE-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'de-DE-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'de-DE-Neural2-D', name: 'Neural2-D (Male)' },
{ value: 'de-DE-Neural2-F', name: 'Neural2-F (Female)' },
{ value: 'de-DE-Polyglot-1', name: 'Polyglot-1 (Male)' },
{ value: 'de-DE-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'de-DE-Standard-C', name: 'Standard-C (Female)' },
{ value: 'de-DE-Standard-D', name: 'Standard-D (Male)' },
{ value: 'de-DE-Studio-B', name: 'Studio-B (Male)' },
{ value: 'de-DE-Studio-C', name: 'Studio-C (Female)' },
],
},
{
value: 'el-GR',
name: 'Greek (Greece)',
voices: [
{ value: 'el-GR-Standard-A', name: 'Standard-A (Female)' },
{ value: 'el-GR-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'gl-ES',
name: 'Galician (Spain)',
voices: [{ value: 'gl-ES-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'gu-IN',
name: 'Gujarati (India)',
voices: [
{ value: 'gu-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'gu-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'gu-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'gu-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
],
},
{
value: 'he-IL',
name: 'Hebrew (Israel)',
voices: [
{ value: 'he-IL-Standard-A', name: 'Standard-A (Female)' },
{ value: 'he-IL-Standard-B', name: 'Standard-B (Male)' },
{ value: 'he-IL-Standard-C', name: 'Standard-C (Female)' },
{ value: 'he-IL-Standard-D', name: 'Standard-D (Male)' },
{ value: 'he-IL-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'he-IL-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'he-IL-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'he-IL-Wavenet-D', name: 'Wavenet-D (Male)' },
],
},
{
value: 'hi-IN',
name: 'Hindi (India)',
voices: [
{ value: 'hi-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'hi-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'hi-IN-Standard-C', name: 'Standard-C (Male)' },
{ value: 'hi-IN-Standard-D', name: 'Standard-D (Female)' },
{ value: 'hi-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'hi-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'hi-IN-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'hi-IN-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'hi-IN-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'hi-IN-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'hi-IN-Neural2-C', name: 'Neural2-C (Male)' },
{ value: 'hi-IN-Neural2-D', name: 'Neural2-D (Female)' },
],
},
{
value: 'hu-HU',
name: 'Hungarian (Hungary)',
voices: [
{ value: 'hu-HU-Standard-A', name: 'Standard-A (Female)' },
{ value: 'hu-HU-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'is-IS',
name: 'Icelandic (Iceland)',
voices: [{ value: 'is-IS-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'id-ID',
name: 'Indonesian (Indonesia)',
voices: [
{ value: 'id-ID-Standard-A', name: 'Standard-A (Female)' },
{ value: 'id-ID-Standard-B', name: 'Standard-B (Male)' },
{ value: 'id-ID-Standard-C', name: 'Standard-C (Male)' },
{ value: 'id-ID-Standard-D', name: 'Standard-D (Female)' },
{ value: 'id-ID-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'id-ID-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'id-ID-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'id-ID-Wavenet-D', name: 'Wavenet-D (Female)' },
],
},
{
value: 'it-IT',
name: 'Italian (Italy)',
voices: [
{ value: 'it-IT-Standard-A', name: 'Standard-A (Female)' },
{ value: 'it-IT-Standard-B', name: 'Standard-B (Female)' },
{ value: 'it-IT-Standard-C', name: 'Standard-C (Male)' },
{ value: 'it-IT-Standard-D', name: 'Standard-D (Male)' },
{ value: 'it-IT-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'it-IT-Wavenet-B', name: 'Wavenet-B (Female)' },
{ value: 'it-IT-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'it-IT-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'it-IT-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'it-IT-Neural2-C', name: 'Neural2-C (Male)' },
],
},
{
value: 'ja-JP',
name: 'Japanese (Japan)',
voices: [
{ value: 'ja-JP-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ja-JP-Standard-B', name: 'Standard-B (Female)' },
{ value: 'ja-JP-Standard-C', name: 'Standard-C (Male)' },
{ value: 'ja-JP-Standard-D', name: 'Standard-D (Male)' },
{ value: 'ja-JP-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ja-JP-Wavenet-B', name: 'Wavenet-B (Female)' },
{ value: 'ja-JP-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'ja-JP-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'ja-JP-Neural2-B', name: 'Neural2-B (Female)' },
{ value: 'ja-JP-Neural2-C', name: 'Neural2-C (Male)' },
{ value: 'ja-JP-Neural2-D', name: 'Neural2-D (Male)' },
],
},
{
value: 'kn-IN',
name: 'Kannada (India)',
voices: [
{ value: 'kn-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'kn-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'kn-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'kn-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
],
},
{
value: 'ko-KR',
name: 'Korean (South Korea)',
voices: [
{ value: 'ko-KR-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ko-KR-Standard-B', name: 'Standard-B (Female)' },
{ value: 'ko-KR-Standard-C', name: 'Standard-C (Male)' },
{ value: 'ko-KR-Standard-D', name: 'Standard-D (Male)' },
{ value: 'ko-KR-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ko-KR-Wavenet-B', name: 'Wavenet-B (Female)' },
{ value: 'ko-KR-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'ko-KR-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'ko-KR-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'ko-KR-Neural2-B', name: 'Neural2-B (Female)' },
{ value: 'ko-KR-Neural2-C', name: 'Neural2-C (Male)' },
],
},
{
value: 'lv-LV',
name: 'Latvian (Latvia)',
voices: [{ value: 'lv-LV-Standard-A', name: 'Standard-A (Male)' }],
},
{
value: 'lt-LT',
name: 'Lithuanian (Lithuania)',
voices: [{ value: 'lt-LT-Standard-A', name: 'Standard-A (Male)' }],
},
{
value: 'cmn-CN',
name: 'Mandarin Chinese',
voices: [
{ value: 'cmn-CN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'cmn-CN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'cmn-CN-Standard-C', name: 'Standard-C (Male)' },
{ value: 'cmn-CN-Standard-D', name: 'Standard-D (Female)' },
{ value: 'cmn-CN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'cmn-CN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'cmn-CN-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'cmn-CN-Wavenet-D', name: 'Wavenet-D (Female)' },
],
},
{
value: 'cmn-TW',
name: 'Mandarin Chinese (Traditional)',
voices: [
{ value: 'cmn-TW-Standard-A-Alpha', name: 'Standard-A-Alpha (Female)' },
{ value: 'cmn-TW-Standard-B-Alpha', name: 'Standard-B-Alpha (Male)' },
{ value: 'cmn-TW-Standard-C-Alpha', name: 'Standard-C-Alpha (Male)' },
{ value: 'cmn-TW-Wavenet-A-Alpha', name: 'Wavenet-A-Alpha (Female)' },
{ value: 'cmn-TW-Wavenet-B-Alpha', name: 'Wavenet-B-Alpha (Male)' },
{ value: 'cmn-TW-Wavenet-C-Alpha', name: 'Wavenet-C-Alpha (Male)' },
],
},
{
value: 'ms-MY',
name: 'Malay (Malaysia)',
voices: [
{ value: 'ms-MY-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ms-MY-Standard-B', name: 'Standard-B (Male)' },
{ value: 'ms-MY-Standard-C', name: 'Standard-C (Female)' },
{ value: 'ms-MY-Standard-D', name: 'Standard-D (Male)' },
{ value: 'ms-MY-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ms-MY-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'ms-MY-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'ms-MY-Wavenet-D', name: 'Wavenet-D (Male)' },
],
},
{
value: 'ml-IN',
name: 'Malayalam (India)',
voices: [
{ value: 'ml-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ml-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'ml-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ml-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'ml-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'ml-IN-Wavenet-D', name: 'Wavenet-D (Male)' },
],
},
{
value: 'mr-IN',
name: 'Marathi (India)',
voices: [
{ value: 'mr-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'mr-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'mr-IN-Standard-C', name: 'Standard-C (Female)' },
{ value: 'mr-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'mr-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'mr-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
],
},
{
value: 'nb-NO',
name: 'Norwegian (Norway)',
voices: [
{ value: 'nb-NO-Standard-A', name: 'Standard-A (Female)' },
{ value: 'nb-NO-Standard-B', name: 'Standard-B (Male)' },
{ value: 'nb-NO-Standard-C', name: 'Standard-C (Female)' },
{ value: 'nb-NO-Standard-D', name: 'Standard-D (Male)' },
{ value: 'nb-no-Standard-E', name: 'Standard-E (Female)' },
{ value: 'nb-NO-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'nb-NO-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'nb-NO-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'nb-NO-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'nb-no-Wavenet-E', name: 'Wavenet-E (Female)' },
],
},
{
value: 'nl-BE',
name: 'Dutch (Belgium)',
voices: [
{ value: 'nl-BE-Standard-A', name: 'Standard-A (Female)' },
{ value: 'nl-BE-Standard-B', name: 'Standard-B (Male)' },
{ value: 'nl-BE-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'nl-BE-Wavenet-B', name: 'Wavenet-B (Male)' },
],
},
{
value: 'pl-PL',
name: 'Polish (Poland)',
voices: [
{ value: 'pl-PL-Standard-A', name: 'Standard-A (Female)' },
{ value: 'pl-PL-Standard-B', name: 'Standard-B (Male)' },
{ value: 'pl-PL-Standard-C', name: 'Standard-C (Male)' },
{ value: 'pl-PL-Standard-D', name: 'Standard-D (Female)' },
{ value: 'pl-PL-Standard-E', name: 'Standard-E (Female)' },
{ value: 'pl-PL-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'pl-PL-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'pl-PL-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'pl-PL-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'pl-PL-Wavenet-E', name: 'Wavenet-E (Female)' },
],
},
{
value: 'pa-IN',
name: 'Punjabi (India)',
voices: [
{ value: 'pa-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'pa-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'pa-IN-Standard-C', name: 'Standard-C (Female)' },
{ value: 'pa-IN-Standard-D', name: 'Standard-D (Male)' },
{ value: 'pa-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'pa-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'pa-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'pa-IN-Wavenet-D', name: 'Wavenet-D (Male)' },
],
},
{
value: 'pt-BR',
name: 'Portuguese (Brazil)',
voices: [
{ value: 'pt-BR-Standard-A', name: 'Standard-A (Female)' },
{ value: 'pt-BR-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'pt-BR-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'pt-BR-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'pt-BR-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'pt-BR-Standard-B', name: 'Standard-B (Male)' },
{ value: 'pt-BR-Standard-C', name: 'Standard-C (Female)' },
{ value: 'pt-BR-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'pt-BR-Wavenet-C', name: 'Wavenet-C (Female)' },
],
},
{
value: 'pt-PT',
name: 'Portuguese (Portugal)',
voices: [
{ value: 'pt-PT-Standard-A', name: 'Standard-A (Female)' },
{ value: 'pt-PT-Standard-B', name: 'Standard-B (Male)' },
{ value: 'pt-PT-Standard-C', name: 'Standard-C (Male)' },
{ value: 'pt-PT-Standard-D', name: 'Standard-D (Female)' },
{ value: 'pt-PT-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'pt-PT-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'pt-PT-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'pt-PT-Wavenet-D', name: 'Wavenet-D (Female)' },
],
},
{
value: 'ro-RO',
name: 'Romanian (Romania)',
voices: [
{ value: 'ro-RO-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ro-RO-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'ru-RU',
name: 'Russian (Russia)',
voices: [
{ value: 'ru-RU-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ru-RU-Standard-B', name: 'Standard-B (Male)' },
{ value: 'ru-RU-Standard-C', name: 'Standard-C (Female)' },
{ value: 'ru-RU-Standard-D', name: 'Standard-D (Male)' },
{ value: 'ru-RU-Standard-E', name: 'Standard-E (Female)' },
{ value: 'ru-RU-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ru-RU-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'ru-RU-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'ru-RU-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'ru-RU-Wavenet-E', name: 'Wavenet-E (Female)' },
],
},
{
value: 'sk-SK',
name: 'Slovak (Slovakia)',
voices: [
{ value: 'sk-SK-Standard-A', name: 'Standard-A (Female)' },
{ value: 'sk-SK-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'sr-RS',
name: 'Serbian (Cyrillic)',
voices: [{ value: 'sr-RS-Standard-A', name: 'Standard-A (Female)' }],
},
{
value: 'es-ES',
name: 'Spanish (Spain)',
voices: [
{ value: 'es-ES-Standard-A', name: 'Standard-A (Female)' },
{ value: 'es-ES-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'es-ES-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'es-ES-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'es-ES-Neural2-D', name: 'Neural2-D (Female)' },
{ value: 'es-ES-Neural2-E', name: 'Neural2-E (Female)' },
{ value: 'es-ES-Neural2-F', name: 'Neural2-F (Male)' },
{ value: 'es-ES-Polyglot-1', name: 'Polyglot-1 (Male)' },
{ value: 'es-ES-Standard-B', name: 'Standard-B (Male)' },
{ value: 'es-ES-Standard-C', name: 'Standard-C (Female)' },
{ value: 'es-ES-Standard-D', name: 'Standard-D (Female)' },
{ value: 'es-ES-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'es-ES-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'es-ES-Wavenet-D', name: 'Wavenet-D (Female)' },
],
},
{
value: 'es-US',
name: 'Spanish (US)',
voices: [
{ value: 'es-US-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'es-US-Neural2-B', name: 'Neural2-B (Male)' },
{ value: 'es-US-Neural2-C', name: 'Neural2-C (Male)' },
{ value: 'es-US-Studio-B', name: 'Studio-B (Male)' },
{ value: 'es-US-Polyglot-1', name: 'Polyglot-1 (Male)' },
{ value: 'es-US-News-D', name: 'News-D (Male)' },
{ value: 'es-US-News-E', name: 'News-E (Male)' },
{ value: 'es-US-News-F', name: 'News-F (Female)' },
{ value: 'es-US-News-G', name: 'News-G (Female)' },
{ value: 'es-US-Standard-A', name: 'Standard-A (Female)' },
{ value: 'es-US-Standard-B', name: 'Standard-B (Male)' },
{ value: 'es-US-Standard-C', name: 'Standard-C (Male)' },
{ value: 'es-US-Studio-B', name: 'Studio-B (Male)' },
{ value: 'es-US-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'es-US-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'es-US-Wavenet-C', name: 'Wavenet-C (Male)' },
],
},
{
value: 'sv-SE',
name: 'Swedish (Sweden)',
voices: [
{ value: 'sv-SE-Standard-A', name: 'Standard-A (Female)' },
{ value: 'sv-SE-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'sv-SE-Standard-B', name: 'Standard-B (Female)' },
{ value: 'sv-SE-Standard-C', name: 'Standard-C (Female)' },
{ value: 'sv-SE-Standard-D', name: 'Standard-D (Male)' },
{ value: 'sv-SE-Standard-E', name: 'Standard-E (Male)' },
{ value: 'sv-SE-Wavenet-B', name: 'Wavenet-B (Female)' },
{ value: 'sv-SE-Wavenet-C', name: 'Wavenet-C (Male)' },
{ value: 'sv-SE-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'sv-SE-Wavenet-E', name: 'Wavenet-E (Male)' },
],
},
{
value: 'ta-IN',
name: 'Tamil (India)',
voices: [
{ value: 'ta-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'ta-IN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'ta-IN-Standard-C', name: 'Standard-C (Female)' },
{ value: 'ta-IN-Standard-D', name: 'Standard-D (Male)' },
{ value: 'ta-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'ta-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'ta-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'ta-IN-Wavenet-D', name: 'Wavenet-D (Male)' },
],
},
{
value: 'te-IN',
name: 'Telugu (India)',
voices: [
{ value: 'te-IN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'te-IN-Standard-B', name: 'Standard-B (Male)' },
],
},
{
value: 'tr-TR',
name: 'Turkish (Turkey)',
voices: [
{ value: 'tr-TR-Standard-A', name: 'Standard-A (Female)' },
{ value: 'tr-TR-Standard-B', name: 'Standard-B (Male)' },
{ value: 'tr-TR-Standard-C', name: 'Standard-C (Female)' },
{ value: 'tr-TR-Standard-D', name: 'Standard-D (Female)' },
{ value: 'tr-TR-Standard-E', name: 'Standard-E (Male)' },
{ value: 'tr-TR-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'tr-TR-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'tr-TR-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'tr-TR-Wavenet-D', name: 'Wavenet-D (Female)' },
{ value: 'tr-TR-Wavenet-E', name: 'Wavenet-E (Male)' },
],
},
{
value: 'uk-UA',
name: 'Ukrainian (Ukraine)',
voices: [
{ value: 'uk-UA-Standard-A', name: 'Standard-A (Female)' },
{ value: 'uk-UA-Wavenet-A', name: 'Wavenet-A (Female)' },
],
},
{
value: 'th-TH',
name: 'Thai (Thailand)',
voices: [
{ value: 'th-TH-Neural2-C', name: 'Neural2-C (Female)' },
{ value: 'th-TH-Standard-A', name: 'Standard-A (Female)' },
],
},
{
value: 'vi-VN',
name: 'Vietnamese (Vietnam)',
voices: [
{ value: 'vi-VN-Standard-A', name: 'Standard-A (Female)' },
{ value: 'vi-VN-Standard-B', name: 'Standard-B (Male)' },
{ value: 'vi-VN-Standard-C', name: 'Standard-C (Female)' },
{ value: 'vi-VN-Standard-D', name: 'Standard-D (Male)' },
{ value: 'vi-VN-Wavenet-A', name: 'Wavenet-A (Female)' },
{ value: 'vi-VN-Wavenet-B', name: 'Wavenet-B (Male)' },
{ value: 'vi-VN-Wavenet-C', name: 'Wavenet-C (Female)' },
{ value: 'vi-VN-Wavenet-D', name: 'Wavenet-D (Male)' },
{ value: 'vi-VN-Neural2-A', name: 'Neural2-A (Female)' },
{ value: 'vi-VN-Neural2-D', name: 'Neural2-D (Male)' },
],
},
{
value: 'yue-HK',
name: 'Chinese (Hong Kong)',
voices: [
{ value: 'yue-HK-Standard-A', name: 'Standard-A (Female)' },
{ value: 'yue-HK-Standard-B', name: 'Standard-B (Male)' },
{ value: 'yue-HK-Standard-C', name: 'Standard-C (Female)' },
{ value: 'yue-HK-Standard-D', name: 'Standard-D (Male)' },
],
},
];

View File

@@ -0,0 +1,167 @@
module.exports = [
{
value: 'de-DE',
name: 'German (Germany)',
voices: [
{ value: 'de-DE_DieterVoice', name: 'Dieter (Male): Standard German' },
{
value: 'de-DE_DieterV2Voice',
name: 'Dieter 2 (Male): Standard German',
},
{
value: 'de-DE_DieterV3Voice',
name: 'Dieter 3 (Male): Standard German',
},
{ value: 'de-DE_ErikaV3Voice', name: 'Erika (Female): Standard German' },
{ value: 'de-DE_BirgitVoice', name: 'Brigit (Female): Standard German' },
{
value: 'de-DE_BirgitV2Voice',
name: 'Brigit 2 (Female): Standard German',
},
{
value: 'de-DE_BirgitV3Voice',
name: 'Brigit 3 (Female): Standard German',
},
],
},
{
value: 'en-US',
name: 'English (US)',
voices: [
{
value: 'en-US_MichaelExpressive',
name: 'Michael (Male): American English - Expressive',
},
{ value: 'en-US_MichaelVoice', name: 'Michael (Male): American English' },
{
value: 'en-US_MichaelV2Voice',
name: 'Michael 2 (Male): American English',
},
{
value: 'en-US_MichaelV3Voice',
name: 'Michael 3 (Male): American English',
},
{ value: 'en-US_HenryV3Voice', name: 'Henry (Male): American English' },
{ value: 'en-US_EmilyV3Voice', name: 'Emily (Female): American English' },
{
value: 'en-US_OliviaV3Voice',
name: 'Olivia (Female): American English',
},
{
value: 'en-US_AllisonExpressive',
name: 'Allison (Female): American English - Expressive',
},
{
value: 'en-US_AllisonVoice',
name: 'Allison (Female): American English',
},
{
value: 'en-US_AllisonV2Voice',
name: 'Allison 2 (Female): American English',
},
{
value: 'en-US_AllisonV3Voice',
name: 'Allison 3 (Female): American English',
},
{
value: 'en-US_LisaExpressive',
name: 'Lisa (Female): American English - Expressive',
},
{ value: 'en-US_LisaVoice', name: 'Lisa (Female): American English' },
{ value: 'en-US_LisaV2Voice', name: 'Lisa 2 (Female): American English' },
{ value: 'en-US_LisaV3Voice', name: 'Lisa 3 (Female): American English' },
{ value: 'en-US_KevinV3Voice', name: 'Kevin (Male): American English' },
{
value: 'en-US_EmmaExpressive',
name: 'Emma (Female): American English - Expressive',
},
],
},
{
value: 'en-GB',
name: 'English (GB)',
voices: [
{ value: 'en-GB_JamesV3Voice', name: 'James (Male)' },
{ value: 'en-GB_KateVoice', name: 'Kate (Female)' },
{ value: 'en-GB_KateV3Voice', name: 'Kate 2 (Female)' },
{ value: 'en-GB_CharlotteV3Voice', name: 'Charlotte (Female)' },
],
},
{
value: 'es-US',
name: 'Spanish (North America)',
voices: [
{
value: 'es-US_SofiaVoice',
name: 'Sofia (Female): North American Spanish',
},
{
value: 'es-US_SofiaV3Voice',
name: 'Sofia 2 (Female): North American Spanish',
},
],
},
{
value: 'es-LA',
name: 'Spanish (Latin America)',
voices: [
{
value: 'es-LA_SofiaVoice',
name: 'Sofia (Female): Latin American Spanish',
},
{
value: 'es-LA_SofiaV3Voice',
name: 'Sofia 2 (Female): Latin American Spanish',
},
],
},
{
value: 'es-ES',
name: 'Spanish (Castilian)',
voices: [
{ value: 'es-ES_LauraVoice', name: 'Laura (Female)' },
{ value: 'es-ES_LauraV3Voice', name: 'Laura 2 (Female)' },
{ value: 'es-ES_EnriqueVoice', name: 'Enrique (Male)' },
{ value: 'es-ES_EnriqueV3Voice', name: 'Enrique 2 (Male)' },
],
},
{
value: 'fr-FR',
name: 'French (FR)',
voices: [
{ value: 'fr-FR_NicolasV3Voice', name: 'Nicolas (Male)' },
{ value: 'fr-FR_ReneeVoice', name: 'Renee (Female)' },
{ value: 'fr-FR_ReneeV3Voice', name: 'Renee 2 (Female)' },
],
},
{
value: 'fr-CA',
name: 'French (CA)',
voices: [{ value: 'fr-CA_LouiseV3Voice', name: 'Louise (Female)' }],
},
{
value: 'it-IT',
name: 'Italian',
voices: [
{ value: 'it-IT_FrancescaVoice', name: 'Francesca (Female)' },
{ value: 'it-IT_FrancescaV2Voice', name: 'Francesca 2 (Female)' },
{ value: 'it-IT_FrancescaV3Voice', name: 'Francesca 3 (Female)' },
],
},
{
value: 'pt-BR',
name: 'Portuguese (Brazil)',
voices: [
{ value: 'pt-BR_IsabelaVoice', name: 'Isabela (Female)' },
{ value: 'pt-BR_IsabelaV3Voice', name: 'Isabela 2 (Female)' },
],
},
{
value: 'ja-JP',
name: 'Japanese',
voices: [
{ value: 'ja-JP_EmiVoice', name: 'Emi (Female)' },
{ value: 'ja-JP_EmiV3Voice', name: 'Emi 2 (Female)' },
],
},
];

View 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'
}
];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
const TtsAwsLanguagesVoiceRaw = require('./tts-microsoft-raw');
const languagesVoices = [];
TtsAwsLanguagesVoiceRaw.forEach((data) => {
const lang = languagesVoices.find((l) => {
return l.value === data.Locale;
});
if (!lang) {
languagesVoices.push({
value: data.Locale,
name: data.LocaleName,
voices: TtsAwsLanguagesVoiceRaw
.filter((d) => {
return d.Locale === data.Locale;
})
.map((d) => {
return {
value: d.ShortName,
name: `${d.DisplayName} (${d.Gender})`,
};
}),
});
}
});
module.exports = languagesVoices;

View File

@@ -0,0 +1,35 @@
module.exports = [
{
name: 'Sonic',
value: 'sonic',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{
name: 'Sonic 2',
value: 'sonic-2',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{
name: 'Sonic Turbo',
value: 'sonic-turbo',
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']
},
];

View File

@@ -0,0 +1,68 @@
module.exports = [
{
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'
},
];

View File

@@ -0,0 +1,12 @@
module.exports = [
{ name: 'Turbo v2', value: 'eleven_turbo_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' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'TTS-1', value: 'tts-1' },
{ name: 'TTS-1-HD', value: 'tts-1-hd' },
{ name: 'GPT-4o-Mini-TTS', value: 'gpt-4o-mini-tts' },
];

View File

@@ -0,0 +1,8 @@
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' },
{ name: 'Dialog 1.0', value: 'PlayDialog'}
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'Mist', value: 'mist' },
{ name: 'Mistv2', value: 'mistv2' },
{ name: 'V1', value: 'v1' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'TTS-1', value: 'tts-1' },
{ name: 'TTS-1-HD', value: 'tts-1-hd' },
{ name: 'GPT-4o mini TTS', value: 'gpt-4o-mini-tts' },
];

View File

@@ -0,0 +1,958 @@
module.exports = [
{
value: 'ar-WW',
name: 'Arabic (Worldwide)',
voices: [
{
value: 'Laila - standard',
name: 'Laila (standard)',
model: 'standard',
},
{
value: 'Tarik - standard',
name: 'Tarik (standard)',
model: 'standard',
},
{
value: 'Miriam - standard',
name: 'Miriam (standard)',
model: 'standard',
},
],
},
{
value: 'eu-ES',
name: 'Basque (Spain)',
voices: [{ value: 'Miren', name: 'Miren (standard)', model: 'standard' }],
},
{
value: 'bn-IN',
name: 'Bengali (India)',
voices: [
{ value: 'Paya - standard', name: 'Paya (standard)', model: 'standard' },
],
},
{
value: 'bho-IN',
name: 'Bhojpuri (India)',
voices: [
{ value: 'Jaya - standard', name: 'Jaya (standard)', model: 'standard' },
],
},
{
value: 'bg-BG',
name: 'Bulgarian (Bulgaria)',
voices: [
{
value: 'Daria - standard',
name: 'Daria (standard)',
model: 'standard',
},
],
},
{
value: 'yue-HK',
name: 'Cantonese (Hong Kong)',
voices: [
{
value: 'Sinji-Ml - standard',
name: 'Sinji-Ml (standard)',
model: 'standard',
},
],
},
{
value: 'ca-ES',
name: 'Catalan (Spain)',
voices: [
{
value: 'Jordi - standard',
name: 'Jordi (standard)',
model: 'standard',
},
{
value: 'Montserrat - standard',
name: 'Montserrat (standard)',
model: 'enhanced',
},
],
},
{
value: 'yue-HK',
name: 'Croatian (Croatia)',
voices: [
{ value: 'Lana - standard', name: 'Lana (standard)', model: 'standard' },
],
},
{
value: 'cs-CZ',
name: 'Czech (Czech Republic)',
voices: [
{
value: 'Iveta - standard',
name: 'Iveta (standard)',
model: 'standard',
},
{
value: 'Zuzana - standard',
name: 'Zuzana (standard)',
model: 'standard',
},
{
value: 'Zuzana-ml - enhanced',
name: 'Zuzana (enhanced)',
model: 'enhanced',
},
],
},
{
value: 'da-DK',
name: 'Danish (Denmark)',
voices: [
{
value: 'Magnus - standard',
name: 'Magnus (standard)',
model: 'standard',
},
{ value: 'Sara - standard', name: 'Sara (standard)', model: 'standard' },
],
},
{
value: 'nl-BE',
name: 'Dutch (Belgium)',
voices: [
{
value: 'Ellen - standard',
name: 'Ellen (standard)',
model: 'standard',
},
],
},
{
value: 'nl-NL',
name: 'Dutch (Belgium)',
voices: [
{
value: 'Claire-Ml - standard',
name: 'Claire-Ml (standard)',
model: 'standard',
},
{
value: 'Xander - standard',
name: 'Xander (standard)',
model: 'standard',
},
],
},
{
value: 'en-AU',
name: 'English (Australia)',
voices: [
{
value: 'Karen - standard',
name: 'Karen (standard)',
model: 'standard',
},
{ value: 'Lee - standard', name: 'Lee (standard)', model: 'standard' },
{
value: 'Matilda - enhanced',
name: 'Matilda (enhanced)',
model: 'enhanced',
},
],
},
{
value: 'en-IN',
name: 'English (India)',
voices: [
{
value: 'Isha-Ml - enhanced',
name: 'Isha-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Rishi - standard',
name: 'Rishi (standard)',
model: 'standard',
},
{
value: 'Rishi-Ml - standard',
name: 'Rishi-Ml (standard)',
model: 'standard',
},
{
value: 'Sangeeta - standard',
name: 'Sangeeta (standard)',
model: 'standard',
},
{
value: 'Veena - standard',
name: 'Veena (standard)',
model: 'standard',
},
],
},
{
value: 'en-IE',
name: 'English (Ireland)',
voices: [
{
value: 'Moira - standard',
name: 'Moira (standard)',
model: 'standard',
},
],
},
{
value: 'en-SC',
name: 'English (Scotland)',
voices: [
{
value: 'Fiona - standard',
name: 'Fiona (standard)',
model: 'standard',
},
],
},
{
value: 'en-ZA',
name: 'English (South Africa)',
voices: [
{
value: 'Tessa - standard',
name: 'Tessa (standard)',
model: 'standard',
},
],
},
{
value: 'en-GB',
name: 'English (United Kingdom)',
voices: [
{
value: 'Daniel - standard',
name: 'Daniel (standard)',
model: 'standard',
},
{ value: 'Kate - standard', name: 'Kate (standard)', model: 'standard' },
{
value: 'Malcolm - standard',
name: 'Malcolm (standard)',
model: 'standard',
},
{
value: 'Oliver - standard',
name: 'Oliver (standard)',
model: 'standard',
},
{
value: 'Serena - enhanced',
name: 'Serena (enhanced)',
model: 'enhanced',
},
{
value: 'Simon - standard',
name: 'Simon (standard)',
model: 'standard',
},
{
value: 'Stephanie - standard',
name: 'Stephanie (standard)',
model: 'standard',
},
],
},
{
value: 'en-US',
name: 'English (United States)',
voices: [
{
value: 'Allison - standard',
name: 'Allison (standard)',
model: 'standard',
},
{
value: 'Ava-Ml - enhanced',
name: 'Ava-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Chloe - standard',
name: 'Chloe (standard)',
model: 'standard',
},
{ value: 'Evan - enhanced', name: 'Evan (enhanced)', model: 'enhanced' },
{
value: 'Nathan - enhanced',
name: 'Nathan (enhanced)',
model: 'enhanced',
},
{
value: 'Evelyn - standard',
name: 'Evelyn (standard)',
model: 'standard',
},
{
value: 'Nolan - standard',
name: 'Nolan (standard)',
model: 'standard',
},
{
value: 'Samantha - standard',
name: 'Samantha (standard)',
model: 'standard',
},
{
value: 'Susan - standard',
name: 'Susan (standard)',
model: 'standard',
},
{ value: 'Tom - standard', name: 'Tom (standard)', model: 'standard' },
{
value: 'Zoe-Ml - enhanced',
name: 'Zoe-Ml (enhanced)',
model: 'enhanced',
},
],
},
{
value: 'fi-FI',
name: 'Finnish (Finland)',
voices: [
{ value: 'Onni - standard', name: 'Onni (standard)', model: 'standard' },
{ value: 'Satu - standard', name: 'Satu (standard)', model: 'standard' },
],
},
{
value: 'fr-BE',
name: 'French (Belgium)',
voices: [
{ value: 'Aude - standard', name: 'Aude (standard)', model: 'standard' },
],
},
{
value: 'fr-CA',
name: 'French (Canada)',
voices: [
{
value: 'Amelie-Ml - enhanced',
name: 'Amelie-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Chantal - standard',
name: 'Chantal (standard)',
model: 'standard',
},
{
value: 'Nicolas - standard',
name: 'Nicolas (standard)',
model: 'standard',
},
],
},
{
value: 'fr-FR',
name: 'French (France)',
voices: [
{
value: 'Audrey-Ml - enhanced',
name: 'Audrey-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Aurelie - standard',
name: 'Aurelie (standard)',
model: 'standard',
},
{
value: 'Thomas - standard',
name: 'Thomas (standard)',
model: 'standard',
},
],
},
{
value: 'gl-ES',
name: 'Galician (Spain)',
voices: [
{
value: 'Carmela - standard',
name: 'Carmela (standard)',
model: 'standard',
},
],
},
{
value: 'de-DE',
name: 'German (Germany)',
voices: [
{
value: 'Anna-Ml - enhanced',
name: 'Anna-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Markus - standard',
name: 'Markus (standard)',
model: 'standard',
},
{
value: 'Petra-Ml - enhanced',
name: 'Petra-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Viktor - standard',
name: 'Viktor (standard)',
model: 'standard',
},
{
value: 'Yannick - standard',
name: 'Yannick (standard)',
model: 'standard',
},
],
},
{
value: 'el-GR',
name: 'Greek (Greece)',
voices: [
{
value: 'Melina - standard',
name: 'Melina (standard)',
model: 'standard',
},
{
value: 'Nikos - standard',
name: 'Nikos (standard)',
model: 'standard',
},
],
},
{
value: 'he-IL',
name: 'Hebrew (Israel)',
voices: [
{
value: 'Carmit - standard',
name: 'Carmit (standard)',
model: 'standard',
},
],
},
{
value: 'hi-IN',
name: 'Hindi (India)',
voices: [
{
value: 'Kiyara-Ml - enhanced',
name: 'Kiyara-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Lekha - standard',
name: 'Lekha (standard)',
model: 'standard',
},
{ value: 'neel - standard', name: 'Neel (standard)', model: 'standard' },
{
value: 'Neel-Ml - standard',
name: 'Neel-Ml (standard)',
model: 'standard',
},
],
},
{
value: 'hu-HU',
name: 'Hungarian (Hungary)',
voices: [
{
value: 'Mariska - standard',
name: 'Mariska (standard)',
model: 'standard',
},
],
},
{
value: 'id-ID',
name: 'Indonesian (Indonesia)',
voices: [
{
value: 'Damayanti - standard',
name: 'Damayanti (standard)',
model: 'standard',
},
],
},
{
value: 'it-IT',
name: 'Italian (Italy)',
voices: [
{ value: 'Emma - enhanced', name: 'Emma (enhanced)', model: 'enhanced' },
{
value: 'Federica-Ml - standard',
name: 'Federica-Ml (standard)',
model: 'standard',
},
{ value: 'Luca - standard', name: 'Luca (standard)', model: 'standard' },
{
value: 'Neel-Ml - standard',
name: 'Neel-Ml (standard)',
model: 'standard',
},
{
value: 'Paola - standard',
name: 'Paola (standard)',
model: 'standard',
},
],
},
{
value: 'ja-JP',
name: 'Japanese (Japan)',
voices: [
{
value: 'Ayane - standard',
name: 'Ayane (standard)',
model: 'standard',
},
{
value: 'Daisuke - standard',
name: 'Daisuke (standard)',
model: 'standard',
},
{
value: 'Ichiro - standard',
name: 'Ichiro (standard)',
model: 'standard',
},
{
value: 'Koharu - standard',
name: 'Koharu (standard)',
model: 'standard',
},
{
value: 'Kyoko - standard',
name: 'Kyoko (standard)',
model: 'standard',
},
{
value: 'Mizuki - standard',
name: 'Mizuki (standard)',
model: 'standard',
},
{
value: 'Otoya - standard',
name: 'Otoya (standard)',
model: 'standard',
},
{
value: 'Sakura - standard',
name: 'Sakura (standard)',
model: 'standard',
},
{
value: 'Seiji - standard',
name: 'Seiji (standard)',
model: 'standard',
},
],
},
{
value: 'kn-IN',
name: 'Kannada (India)',
voices: [
{
value: 'Alpana - standard',
name: 'Alpana (standard)',
model: 'standard',
},
],
},
{
value: 'ko-KR',
name: 'Korean (South Korea)',
voices: [
{ value: 'Jina - enhanced', name: 'Jina (enhanced)', model: 'enhanced' },
{ value: 'Sora - standard', name: 'Sora (standard)', model: 'standard' },
{ value: 'Yuna - standard', name: 'Yuna (standard)', model: 'standard' },
{
value: 'Yuna-Ml - enhanced',
name: 'Yuna-Ml (enhanced)',
model: 'enhanced',
},
],
},
{
value: 'zlm-MY',
name: 'Malay (Malaysia)',
voices: [
{
value: 'Amira - standard',
name: 'Amira (standard)',
model: 'standard',
},
],
},
{
value: 'zh-CN',
name: 'Mandarin (China)',
voices: [
{
value: 'Lili-Ml - enhanced',
name: 'Lili-Ml (enhanced)',
model: 'enhanced',
},
{
value: 'Binbin-Ml - standard',
name: 'Binbin-Ml (standard)',
model: 'standard',
},
{
value: 'Lilian-Ml - standard',
name: 'Lilian-Ml (standard)',
model: 'standard',
},
{
value: 'Lisheng-Ml - standard',
name: 'Lisheng-Ml (standard)',
model: 'standard',
},
{
value: 'Tiantian-Ml - standard',
name: 'Tiantian-Ml (standard)',
model: 'standard',
},
{
value: 'Tingting-Ml - standard',
name: 'Tingting-Ml (standard)',
model: 'standard',
},
],
},
{
value: 'cmn-TW',
name: 'Mandarin (Taiwan)',
voices: [
{
value: 'Meijia-Ml - standard',
name: 'Meijia-Ml (standard)',
model: 'standard',
},
],
},
{
value: 'mr-IN',
name: 'Marathi (India)',
voices: [
{
value: 'Ananya - standard',
name: 'Ananya (standard)',
model: 'standard',
},
],
},
{
value: 'nb-NO',
name: 'Norwegian Bokmål (Norway)',
voices: [
{
value: 'Henrik - standard',
name: 'Henrik (standard)',
model: 'standard',
},
{ value: 'Nora - standard', name: 'Nora (standard)', model: 'standard' },
],
},
{
value: 'pl-PL',
name: 'Polish (Poland)',
voices: [
{ value: 'Ewa - enhanced', name: 'Ewa (enhanced)', model: 'enhanced' },
{
value: 'Krzysztof - standard',
name: 'Krzysztof (standard)',
model: 'standard',
},
{
value: 'Zosia - standard',
name: 'Zosia (standard)',
model: 'standard',
},
],
},
{
value: 'pt-BR',
name: 'Portuguese (Brazil)',
voices: [
{
value: 'luciana - enhanced',
name: 'Luciana (enhanced)',
model: 'enhanced',
},
{
value: 'Fernanda - standard',
name: 'Fernanda (standard)',
model: 'standard',
},
{
value: 'Felipe - standard',
name: 'Felipe (standard)',
model: 'standard',
},
],
},
{
value: 'pt-PT',
name: 'Portuguese (Portugal)',
voices: [
{
value: 'Catarina - standard',
name: 'Catarina (standard)',
model: 'standard',
},
{
value: 'Joana - standard',
name: 'Joana (standard)',
model: 'standard',
},
{
value: 'Joaquim - standard',
name: 'Joaquim (standard)',
model: 'standard',
},
],
},
{
value: 'ro-RO',
name: 'Romanian (Romania)',
voices: [
{
value: 'Ioana - standard',
name: 'Ioana (standard)',
model: 'standard',
},
],
},
{
value: 'ru-RU',
name: 'Russian (Russia)',
voices: [
{
value: 'Katya - standard',
name: 'Katya (standard)',
model: 'standard',
},
{
value: 'Katya-Ml - standard',
name: 'Katya-Ml (standard)',
model: 'standard',
},
{
value: 'Milena - standard',
name: 'Milena (standard)',
model: 'standard',
},
{ value: 'yuri - standard', name: 'Yuri (standard)', model: 'standard' },
],
},
{
value: 'sk-SK',
name: 'Slovak (Slovakia)',
voices: [
{
value: 'Laura - standard',
name: 'Laura (standard)',
model: 'standard',
},
],
},
{
value: 'es-AR',
name: 'Spanish (Argentina)',
voices: [
{
value: 'Diego - standard',
name: 'Diego (standard)',
model: 'standard',
},
{
value: 'Isabela - standard',
name: 'Isabela (standard)',
model: 'standard',
},
],
},
{
value: 'es-CL',
name: 'Spanish (Chile)',
voices: [
{
value: 'Francisca - standard',
name: 'Francisca (standard)',
model: 'standard',
},
],
},
{
value: 'es-CO',
name: 'Spanish (Colombia)',
voices: [
{
value: 'Carlos - standard',
name: 'Carlos (standard)',
model: 'standard',
},
{
value: 'Soledad - standard',
name: 'Soledad (standard)',
model: 'standard',
},
{
value: 'Ximena - standard',
name: 'Ximena (standard)',
model: 'standard',
},
],
},
{
value: 'es-MX',
name: 'Spanish (Mexico)',
voices: [
{
value: 'Angelica - standard',
name: 'Angelica (standard)',
model: 'standard',
},
{
value: 'Javier - standard',
name: 'Javier (standard)',
model: 'standard',
},
{ value: 'Juan - standard', name: 'Juan (standard)', model: 'standard' },
{
value: 'Paulina-Ml - enhanced',
name: 'Paulina-Ml (enhanced)',
model: 'enhanced',
},
],
},
{
value: 'es-ES',
name: 'Spanish (Spain)',
voices: [
{
value: 'Jorge - standard',
name: 'Jorge (standard)',
model: 'standard',
},
{
value: 'Marisol-Ml - standard',
name: 'Marisol-Ml (standard)',
model: 'standard',
},
{
value: 'Monica-Ml - standard',
name: 'Monica-Ml (standard)',
model: 'standard',
},
],
},
{
value: 'sv-SE',
name: 'Swedish (Sweden)',
voices: [
{ value: 'Alva - standard', name: 'Alva (standard)', model: 'standard' },
{
value: 'Klara - standard',
name: 'Klara (standard)',
model: 'standard',
},
{
value: 'Oskar - standard',
name: 'Oskar (standard)',
model: 'standard',
},
],
},
{
value: 'ta-IN',
name: 'Tamil (India)',
voices: [
{ value: 'Vani - standard', name: 'Vani (standard)', model: 'standard' },
],
},
{
value: 'te-IN',
name: 'Telugu (India)',
voices: [
{
value: 'Geeta - standard',
name: 'Geeta (standard)',
model: 'standard',
},
],
},
{
value: 'th-TH',
name: 'Thai (Thailand)',
voices: [
{
value: 'Kanya - enhanced',
name: 'Kanya (enhanced)',
model: 'enhanced',
},
{
value: 'Narisa - standard',
name: 'Narisa (standard)',
model: 'standard',
},
],
},
{
value: 'tr-TR',
name: 'Turkish (Turkey)',
voices: [
{
value: 'Cem-Ml - standard',
name: 'Cem-Ml (standard)',
model: 'standard',
},
{
value: 'Yelda - standard',
name: 'Yelda (standard)',
model: 'standard',
},
],
},
{
value: 'uk-UA',
name: 'Ukrainian (Ukraine)',
voices: [
{
value: 'Lesya - standard',
name: 'Lesya (standard)',
model: 'standard',
},
],
},
{
value: 'va-ES',
name: 'Valencian (Spain)',
voices: [
{
value: 'Empar - standard',
name: 'Empar (standard)',
model: 'standard',
},
],
},
{
value: 'vi-VN',
name: 'Vietnamese (Vietnam)',
voices: [
{ value: 'Linh - standard', name: 'Linh (standard)', model: 'standard' },
],
},
];

View File

@@ -0,0 +1,16 @@
module.exports = [
{
value: 'en-US',
name: 'English',
voices: [
{
value: 'English-US.Female-1',
name: 'Female',
},
{
value: 'English-US.Male-1',
name: 'Male',
},
],
},
];

View File

@@ -0,0 +1,710 @@
module.exports = [
{
value: 'en-US',
name: 'English (US)',
voices: [
{
value:
's3://mockingbird-prod/abigail_vo_6661b91f-4012-44e3-ad12-589fbdee9948/voices/speaker/manifest.json',
name: 'Abigail - american, female, narrative, smooth',
},
{
value: 'abram',
name: 'Abram - british, old, male, low, narrative, slow, round',
},
{
value: 'adolfo',
name: 'Adolfo - american, adult, male, neutral, narrative, fast, thick',
},
{
value: 'adrian',
name: 'Adrian - american, old, male, neutral, narrative, fast, thick',
},
{
value: 'ahmed',
name: 'Logan - british, old, male, neutral, narrative, neutral, thick',
},
{
value: 'alex',
name: 'Alex - british, adult, male, high, narrative, slow, thick',
},
{
value: 'alexander',
name: 'Alexander - british, old, male, high, narrative, fast, thick',
},
{
value: 'alfonso',
name: 'Alfonso - american, adult, male, neutral, videos, neutral, gravelly',
},
{
value: 'alphonso',
name: 'Alphonso - american, adult, female, low, videos, neutral, smooth',
},
{
value: 'amado',
name: 'Amado - american, old, male, low, narrative, fast, smooth',
},
{
value: 'anny',
name: 'Anny - american, youth, female, neutral, narrative, neutral, thick',
},
{
value: 'anthony',
name: 'Anthony - american, adult, male, neutral, training, slow, thick',
},
{
value: 'spencer',
name: 'April - british, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'victor',
name: 'Ariana - american, youth, female, high, videos, fast, thick',
},
{
value: 'arthur',
name: 'Arthur - british, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 'aubrey',
name: 'Aubrey - british, adult, male, neutral, videos, neutral, smooth',
},
{
value: 'hipolito',
name: 'Audrey - american, adult, female, low, narrative, slow, round',
},
{
value: 'aurora',
name: 'Aurora - british, adult, female, low, training, slow, round',
},
{
value: 'axel',
name: 'Axel - american, adult, male, neutral, narrative, fast, thick',
},
{
value:
's3://mockingbird-prod/ayla_vo_commercials_d66900d5-69f5-476f-9bd6-8eab2936dda3/voices/speaker/manifest.json',
name: 'Ayla (Advertising) - american, female, advertising',
},
{
value:
's3://mockingbird-prod/ayla_vo_expressive_16095e08-b9e8-429b-947c-47a75e41053b/voices/speaker/manifest.json',
name: 'Ayla (Expressive) - american, female, narrative',
},
{
value:
's3://mockingbird-prod/ayla_vo_meditation_d11dd9da-b5f1-4709-95a6-e6d5dc77614a/voices/speaker/manifest.json',
name: 'Ayla (Meditation) - american, female, meditation',
},
{
value:
's3://mockingbird-prod/ayla_vo_narrative_d8199dfd-b50f-40c7-9d99-e203ba5f4152/voices/speaker/manifest.json',
name: 'Ayla (Narrative) - american, female, narrative',
},
{
value:
's3://mockingbird-prod/ayla_vo_training_e6751ca5-e47c-4c4b-ad05-d3a194417600/voices/speaker/manifest.json',
name: 'Ayla (Training) - american, female, training',
},
{
value: 'benton',
name: 'Benton - american, old, male, high, videos, fast, smooth',
},
{
value: 'bertram',
name: 'Bertram - british, adult, male, low, narrative, neutral, gravelly',
},
{
value: 'bill',
name: 'Harper - american, adult, female, high, videos, fast, smooth',
},
{
// eslint-disable-next-line max-len
value:'s3://mockingbird-prod/nathan_drake_carmelo_pampillonio_7d540ad6-7d32-41f6-8d53-2584901aa03d/voices/speaker/manifest.json',
name: 'Billy - american, male, gaming',
},
{
value: 'blaine',
name: 'Blaine - british, adult, male, high, narrative, neutral, thick',
},
{
value: 'booker',
name: 'Booker - british, youth, male, neutral, narrative, neutral, round',
},
{
value: 'bret',
name: 'Bret - american, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'bruce',
name: 'Bruce - british, adult, male, high, training, fast, thick',
},
{
value: 'bryan',
name: 'Bryan - american, adult, male, low, videos, fast, gravelly',
},
{
value: 'carlo',
name: 'Carlo - british, adult, male, neutral, advertising, neutral, smooth',
},
{
value: 'carter',
name: 'Carter - american, adult, male, neutral, narrative, neutral, thick',
},
{
value: 'charles',
name: 'Charles - american, adult, male, neutral, narrative, neutral, round',
},
{
value: 'charlotte',
name: 'Charlotte - canadian, adult, female, low, narrative, neutral, smooth',
},
{
value:
's3://voice-cloning-zero-shot/028a32d4-6a79-4ca3-a303-d6559843114b/chris/manifest.json',
name: 'Chris - american, adult, male,',
},
{
value: 'chuck',
name: 'Chuck - british, adult, male, neutral, videos, slow, round',
},
{
value: 'clark',
name: 'Clark - british, old, male, neutral, narrative, slow, smooth',
},
{
value: 'clifton',
name: 'Clifton - american, old, male, high, narrative, neutral, gravelly',
},
{
value: 'hayden',
name: 'Cooper - american, adult, male, neutral, narrative, neutral, round',
},
{
value: 'daisy',
name: 'Daisy - british, adult, female, low, narrative, neutral, gravelly',
},
{
value: 'dane',
name: 'Dane - american, adult, male, neutral, videos, neutral, round',
},
{
value: 'daniel',
name: 'Daniel - canadian, adult, male, low, narrative, neutral, smooth',
},
{
value: 'darnell',
name: 'Darnell - american, youth, male, neutral, narrative, neutral, smooth',
},
{
value: 'daron',
name: 'Daron - american, old, male, low, narrative, slow, round',
},
{
value: 'darrell',
name: 'Darrell - british, adult, male, neutral, advertising, neutral, thick',
},
{
value: 's3://peregrine-voices/a10/manifest.json',
name: 'Davis - american, adult, male,',
},
{
value: 'ignacio',
name: 'Delilah - american, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'denis',
name: 'Eleanor - british, adult, female, neutral, advertising, neutral, smooth',
},
{
value: 'dick',
name: 'Dick - american, adult, male, neutral, training, fast, smooth',
},
{
value: 'domenic',
name: 'Domenic - british, adult, male, high, videos, neutral, thick',
},
{
value: 's3://peregrine-voices/donna_meditation_saad/manifest.json',
name: 'Donna (Meditation) - american, female, meditation',
},
{
value: 's3://peregrine-voices/donna_parrot_saad/manifest.json',
name: 'Donna (Narrative) - american, female, narrative',
},
{
value: 'donovan',
name: 'Donovan - american, adult, male, low, narrative, neutral, smooth',
},
{
value: 'dudley',
name: 'Dudley - american, old, male, low, narrative, fast, smooth',
},
{
value: 'dylan',
name: 'Dylan - british, old, male, high, gaming, slow, smooth',
},
{
value: 'earle',
name: 'Earle - british, adult, male, high, narrative, neutral, gravelly',
},
{
value: 'efren',
name: 'Efren - american, adult, male, neutral, training, slow, thick',
},
{
value: 'denis',
name: 'Eleanor - british, adult, female, neutral, advertising, neutral, smooth',
},
{
value: 'elijah',
name: 'Elijah - american, old, male, neutral, training, neutral, gravelly',
},
{
value: 'ellie',
name: 'Ellie - american, adult, female, low, training, slow, smooth',
},
{
value: 'erasmo',
name: 'Erasmo - american, old, male, low, training, fast, smooth',
},
{
value: 's3://peregrine-voices/evelyn 2 saad parrot/manifest.json',
name: 'Evelyn - american, adult, female, low, videos, neutral, smooth',
},
{
value: 'fletcher',
name: 'Fletcher - british, adult, male, neutral, narrative, fast, gravelly',
},
{
value: 'florencio',
name: 'Madison - british, old, female, neutral, narrative, slow, round',
},
{
value: 'flynn',
name: 'Flynn - british, adult, male, neutral, narrative, fast, round',
},
{
value: 'gabriel',
name: 'Samantha - american, old, female, neutral, narrative, neutral, thick',
},
{
value: 'greg',
name: 'Greg - british, adult, male, high, narrative, slow, round',
},
{
value: 'harold',
name: 'Harold - american, adult, male, neutral, narrative, slow, smooth',
},
{
value: 'bill',
name: 'Harper - american, adult, female, high, videos, fast, smooth',
},
{
value: 'harris',
name: 'Harris - british, adult, male, low, narrative, fast, smooth',
},
{
value: 'harrison',
name: 'Harrison - american, adult, male, neutral, narrative, fast, round',
},
{
value: 'hayden',
name: 'Cooper - american, adult, male, neutral, narrative, neutral, round',
},
{
value: 'hipolito',
name: 'Audrey - american, adult, female, low, narrative, slow, round',
},
{
value:
's3://mockingbird-prod/hook_1_chico_a3e5e83f-08ae-4a9f-825c-7e48d32d2fd8/voices/speaker/manifest.json',
name: 'Hook - american, male, gaming',
},
{
value: 's3://peregrine-voices/hudson saad parrot/manifest.json',
name: 'Hudson - american, adult, male, neutral, videos, neutral, thick',
},
{
value: 'hunter',
name: 'Hunter - british, old, male, high, narrative, fast, round',
},
{
value: 'ignacio',
name: 'Delilah - american, adult, female, neutral, narrative, slow, smooth',
},
{
value: 's3://peregrine-voices/mel28/manifest.json',
name: 'Jack - american, adult, male,',
},
{
value: 'jarrett',
name: 'Jarrett - american, adult, male, low, advertising, slow, smooth',
},
{
value:
's3://voice-cloning-zero-shot/801a663f-efd0-4254-98d0-5c175514c3e8/jennifer/manifest.json',
name: 'Jennifer - american, adult, female,',
},
{
value: 'jerrell',
name: 'Jerrell - american, adult, male, low, narrative, neutral, round',
},
{
value: 'jordan',
name: 'Jordan - american, adult, male, neutral, training, slow, round',
},
{
value:
's3://voice-cloning-zero-shot/dc23bb38-f568-4323-b6fb-7d64f685b97a/joseph/manifest.json',
name: 'Joseph - american, adult, male,',
},
{
value: 'judson',
name: 'Judson - american, adult, male, low, narrative, slow, smooth',
},
{
value: 'lance',
name: 'Lance - british, adult, male, low, videos, neutral, smooth',
},
{
value: 'larry',
name: 'Larry - american, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 's3://peregrine-voices/larry_ads3_parrot_saad/manifest.json',
name: 'Larry (Advertising) - american, adult, male, neutral, advertising, neutral, smooth',
},
{
value:
's3://mockingbird-prod/larry_vo_narrative_4bd5c1bd-f662-4a38-b5b9-76563f7b92ec/voices/speaker/manifest.json',
name: 'Larry (Narrative) - american, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 'lillian',
name: 'Lillian - british, old, female, neutral, training, slow, round',
},
{
value: 'ahmed',
name: 'Logan - british, old, male, neutral, narrative, neutral, thick',
},
{
value: 'lottie',
name: 'Lottie - british, adult, female, low, narrative, slow, smooth',
},
{
value: 'lucius',
name: 'Lucius - british, adult, male, low, narrative, slow, smooth',
},
{
value: 'mickey',
name: 'Madelyn - british, adult, female, neutral, videos, fast, thick',
},
{
value:
's3://voice-cloning-zero-shot/09b5c0cc-a8f4-4450-aaab-3657b9965d0b/podcaster/manifest.json',
name: 'Matt - american, adult, male,',
},
{
value: 's3://peregrine-voices/mel21/manifest.json',
name: 'Melissa - american, adult, female,',
},
{
value: 'micah',
name: 'Micah - british, adult, female, neutral, narrative, neutral, smooth',
},
{
value:
's3://voice-cloning-zero-shot/7c339a9d-370f-4643-adf5-4134e3ec9886/mlae02/manifest.json',
name: 'Michael - american, adult, male,',
},
{
value: 'mickey',
name: 'Madelyn - british, adult, female, neutral, videos, fast, thick',
},
{
value:
's3://voice-cloning-zero-shot/7c38b588-14e8-42b9-bacd-e03d1d673c3c/nicole/manifest.json',
name: 'Nicole - american, adult, female,',
},
{
value: 's3://peregrine-voices/nolan saad parrot/manifest.json',
name: 'Nolan - british, adult, male, high, videos, neutral, round',
},
{
value: 'nova',
name: 'Nova - american, adult, female, whisper, narrative, slow, smooth',
},
{
value: 'oliver',
name: 'Oliver - british, adult, male, high, videos, neutral, round',
},
{
value: 'oscar',
name: 'Oscar - british, adult, male, neutral, narrative, slow, smooth',
},
{
value: 'owen',
name: 'Owen - american, youth, male, high, narrative, neutral, round',
},
{
value: 'pedro',
name: 'Pedro - american, adult, male, neutral, narrative, slow, round',
},
{
value: 'phoebe',
name: 'Phoebe - british, adult, female, high, videos, fast, smooth',
},
{
value: 'randall',
name: 'Randall - british, adult, male, high, narrative, fast, thick',
},
{
value: 'reynaldo',
name: 'Reynaldo - british, old, male, low, narrative, fast, smooth',
},
{
value: 'rodrick',
name: 'Rodrick - american, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 'gabriel',
name: 'Samantha - american, old, female, neutral, narrative, neutral, thick',
},
{
value: 'samuel',
name: 'Samuel - american, old, male, high, narrative, slow, gravelly',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/agent_47_carmelo_pampillonio_58e796e1-0b87-4f3e-8b36-7def6d65ce66/voices/speaker/manifest.json',
name: 'Sarge - american, male, gaming',
},
{
value:
's3://voice-cloning-zero-shot/1f44b3e7-22ea-4c2e-87d0-b4d9c8f1d47d/sophia/manifest.json',
name: 'Sophia - american, adult, female,',
},
{
value: 'spencer',
name: 'April - british, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'stella',
name: 'Stella - british, old, female, neutral, training, slow, round',
},
{
value: 'susan',
name: 'Susan - american, adult, female, high, videos, neutral, round',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/susan_vo_commercials_0f4fa663-6eba-4582-be1e-2d5bde798f1c/voices/speaker/manifest.json',
name: 'Susan (Advertising) - american, adult, female, high, advertising, neutral, round',
},
{
value:
's3://mockingbird-prod/susan_vo_narrative_73051c90-460b-4e54-adab-9235f45c5e5f/voices/speaker/manifest.json',
name: 'Susan (Narrative) - american, adult, female, high, narrative, neutral, round',
},
{
value:
's3://mockingbird-prod/susan_vo_training_46ffcc60-d630-42f6-acfe-4affd003ae7a/voices/speaker/manifest.json',
name: 'Susan (Training) - american, adult, female, high, training, neutral, round',
},
{
value: 'theodore',
name: 'Theodore - american, old, male, neutral, narrative, neutral, gravelly',
},
{
value: 'victor',
name: 'Ariana - american, youth, female, high, videos, fast, thick',
},
{
value: 'wilbert',
name: 'Wilbert - british, adult, male, neutral, narrative, neutral, round',
},
{
value: 'wilbur',
name: 'Wilbur - american, youth, male, neutral, narrative, neutral, smooth',
},
{
value: 'wilfred',
name: 'Wilfred - american, old, male, low, training, slow, smooth',
},
{
value: 's3://peregrine-voices/mel22/manifest.json',
name: 'Will - american, adult, male,',
},
{
value: 'william',
name: 'William - american, adult, male, neutral, videos, neutral, round',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/william_vo_narrative_0eacdff5-6243-4e26-8b3b-66e03458c1d1/voices/speaker/manifest.json',
name: 'William (Narrative) - american, adult, male, neutral, narrative, neutral, round',
},
{
value:
's3://mockingbird-prod/william_vo_training_1b939b71-14fa-41f0-b1db-7d94f194ad0a/voices/speaker/manifest.json',
name: 'William (Training) - american, adult, male, neutral, training, neutral, round',
},
],
},
{
value: 'en-GB',
name: 'English (GB)',
voices: [
{
value: 's3://peregrine-voices/arthur ads parrot saad/manifest.json',
name: 'Arthur (Advertising) - british, adult, male, neutral, advertising, neutral, smooth',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/arthur_vo_meditatoin_211f702d-b185-4115-b8b4-801f8130a38d/voices/speaker/manifest.json',
name: 'Arthur (Meditation) - british, adult, male, neutral, meditation, neutral, smooth',
},
{
value:
's3://mockingbird-prod/arthur_vo_narrative_a33fd610-73a9-4401-9a78-6b8219c68a9e/voices/speaker/manifest.json',
name: 'Arthur (Narrative) - british, adult, male, neutral, narrative, neutral, smooth',
},
{
value:
's3://mockingbird-prod/arthur_vo_training_9281c8fd-c7f0-4445-a148-466292d3d329/voices/speaker/manifest.json',
name: 'Arthur (Training) - british, adult, male, neutral, training, neutral, smooth',
},
{
value:
's3://mockingbird-prod/eileen_vo_5d7b2bcc-d635-4301-97e8-d97c13768514/voices/speaker/manifest.json',
name: 'Eileen - british, female, narrative',
},
{
value: 'frankie',
name: 'Frankie - british, old, male, neutral, training, neutral, thick',
},
{
value:
's3://voice-cloning-zero-shot/418a94fa-2395-4487-81d8-22daf107781f/george/manifest.json',
name: 'George - british, adult, male,',
},
{
value: 'julian',
name: 'Julian - british, adult, male, neutral, videos, neutral, round',
},
{
value:
's3://voice-cloning-zero-shot/0b5b2e4b-5103-425e-8aa0-510dd35226e2/mark/manifest.json',
name: 'Mark - british, adult, male,',
},
{
value: 's3://peregrine-voices/oliver_ads2_parrot_saad/manifest.json',
name: 'Oliver (Advertising) - british, adult, male, high, advertising, neutral, round',
},
{
value:
's3://peregrine-voices/oliver_narrative2_parrot_saad/manifest.json',
name: 'Oliver (Narrative) - british, adult, male, high, narrative, neutral, round',
},
{
value:
's3://mockingbird-prod/oliver_vo_training_6e3f604a-5605-4542-948d-347b0d7546fc/voices/speaker/manifest.json',
name: 'Oliver (Training) - british, adult, male, high, training, neutral, round',
},
{
value:
's3://voice-cloning-zero-shot/820da3d2-3a3b-42e7-844d-e68db835a206/sarah/manifest.json',
name: 'Sarah - british, adult, female,',
},
],
},
{
value: 'en-AU',
name: 'English (AU)',
voices: [
{
value: 's3://peregrine-voices/barry ads parrot saad/manifest.json',
name: 'Barry (Advertising) - australian, male, advertising',
},
{
value:
's3://peregrine-voices/barry narrative parrot saad/manifest.json',
name: 'Barry (Narrative) - australian, male, narrative',
},
{
value: 'frederick',
name: 'Frederick - australian, adult, male, low, narrative, slow, thick',
},
{
value: 's3://peregrine-voices/russell2_parrot_saad/manifest.json',
name: 'Russell - australian, male,',
},
],
},
{
value: 'en-CA',
name: 'English (CA)',
voices: [
{
value: 's3://peregrine-voices/charlotte ads parrot saad/manifest.json',
name: 'Charlotte (Advertising) - canadian, adult, female, low, advertising, neutral, smooth',
},
{
value:
's3://peregrine-voices/charlotte meditation 2 parrot saad/manifest.json',
name: 'Charlotte (Meditation) - canadian, adult, female, low, meditation, neutral, smooth',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/charlotte_vo_narrative_9290be17-ccea-4700-a7fd-a8fe5c49fb20/voices/speaker/manifest.json',
name: 'Charlotte (Narrative) - canadian, adult, female, low, narrative, neutral, smooth',
},
{
value:
's3://peregrine-voices/charlotte_training_parrot_saad/manifest.json',
name: 'Charlotte (Training) - canadian, adult, female, low, training, neutral, smooth',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/olivia_vo_commercials_6e3c384f-15d6-4fe7-b9a4-0cb1d69daeba/voices/speaker/manifest.json',
name: 'Olivia (Advertising) - canadian, female, advertising',
},
{
value: 's3://peregrine-voices/olivia_ads3_parrot_saad/manifest.json',
name: 'Olivia (Narrative) - canadian, female, narrative',
},
{
value:
's3://mockingbird-prod/olivia_vo_training_4376204f-a411-4e5d-a5c0-ce6cc3908052/voices/speaker/manifest.json',
name: 'Olivia (Training) - canadian, female, training',
},
],
},
{
value: 'en-IE',
name: 'English (IE)',
voices: [
{
value: 'florencio',
name: 'Madison - irish, old, female, neutral, narrative, slow, round',
},
],
},
{
value: 'en-NZ',
name: 'English (NZ)',
voices: [
{
value:
's3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json',
name: 'Ruby - australian, adult, female,',
},
],
},
];

View File

@@ -0,0 +1,46 @@
module.exports = [
{
value: 'es-ES',
name: 'Castilian Spanish',
voices: [
{
value: 'david_es_es',
name: 'David-Male',
},
],
},
{
value: 'es-PE',
name: 'Peruvian Spanish',
voices: [
{
value: 'miguel_es_pe',
name: 'Miguel-Male',
},
{
value: 'luz_es_pe',
name: 'Luz-Female',
},
],
},
{
value: 'pt-BR',
name: 'Brazilian Portuguese',
voices: [
{
value: 'bel_pt_br',
name: 'Bel-Female',
},
],
},
{
value: 'ca-ES',
name: 'Catalan',
voices: [
{
value: 'anna_ca',
name: 'Anna-Female',
},
],
},
];

View File

@@ -0,0 +1,39 @@
module.exports = [
{
value: 'en-US',
name: 'English (US)',
voices: [
{ value: '3', name: 'Alana B.' },
{ value: '4', name: 'Ramona J.' },
{ value: '5', name: 'Ramona J. (promo)' },
{ value: '7', name: 'Wade C.' },
{ value: '8', name: 'Sofia H.' },
{ value: '9', name: 'David D.' },
{ value: '11', name: 'Isabel V.' },
{ value: '12', name: 'Ava H.' },
{ value: '13', name: 'Jeremy G.' },
{ value: '14', name: 'Nicole L.' },
{ value: '15', name: 'Paige L.' },
{ value: '16', name: 'Tobin A.' },
{ value: '17', name: 'Kai M.' },
{ value: '18', name: 'Tristan F.' },
{ value: '19', name: 'Patrick K.' },
{ value: '20', name: 'Soifia H. (promo)' },
{ value: '21', name: 'Damian P. (promo)' },
{ value: '22', name: 'Jodi P. (promo)' },
{ value: '23', name: 'Lee M. (promo)' },
{ value: '24', name: 'Selene R. (promo)' },
{ value: '26', name: 'Wade C. (promo)' },
{ value: '27', name: 'Joe F.' },
{ value: '28', name: 'Joe F. (promo)' },
{ value: '29', name: 'Garry J. (character)' },
{ value: '33', name: 'Jude D.' },
{ value: '34', name: 'Eric S. (promo)' },
{ value: '35', name: 'Chase J.' },
{ value: '37', name: 'Steve B. (promo)' },
{ value: '38', name: 'Bella B. (promo)' },
{ value: '39', name: 'Tilda C. (promo)' },
{ value: '41', name: 'Paul B. (promo)' },
],
},
];

View File

@@ -0,0 +1,14 @@
module.exports = [
{
value: 'en-US',
name: 'English',
voices: [
{ value: 'alloy', name: 'Alloy' },
{ value: 'echo', name: 'Echo' },
{ value: 'fable', name: 'Fable' },
{ value: 'onyx', name: 'Onyx' },
{ value: 'nova', name: 'Nova' },
{ value: 'shimmer', name: 'Shimmer' },
],
},
];

File diff suppressed because it is too large Load Diff

16904
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
{
"name": "jambonz-api-server",
"version": "0.8.4",
"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",
@@ -19,51 +20,57 @@
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"@aws-sdk/client-transcribe": "^3.363.0",
"@aws-sdk/client-s3": "^3.363.0",
"@aws-sdk/client-cloudwatch-logs": "^3.750.0",
"@aws-sdk/client-s3": "^3.550.0",
"@aws-sdk/client-transcribe": "^3.549.0",
"@azure/storage-blob": "^12.17.0",
"@deepgram/sdk": "^1.21.0",
"@google-cloud/speech": "^5.2.0",
"@jambonz/db-helpers": "^0.9.0",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/speech-utils": "^0.0.15",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.29",
"@google-cloud/speech": "^6.5.0",
"@google-cloud/storage": "^7.9.0",
"@jambonz/db-helpers": "^0.9.11",
"@jambonz/lamejs": "^1.2.2",
"@soniox/soniox-node": "^1.1.1",
"argon2": "^0.30.3",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.13",
"@jambonz/speech-utils": "^0.2.3",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.72",
"@soniox/soniox-node": "^1.2.2",
"argon2": "^0.40.1",
"assemblyai": "^4.3.4",
"bent": "^7.3.12",
"cors": "^2.8.5",
"debug": "^4.3.4",
"express": "^4.18.1",
"express-rate-limit": "^6.4.0",
"form-data": "^2.5.1",
"helmet": "^5.1.0",
"ibm-watson": "^7.1.2",
"jsonwebtoken": "^9.0.0",
"mailgun.js": "^9.1.2",
"microsoft-cognitiveservices-speech-sdk": "^1.24.1",
"mysql2": "^2.3.3",
"nocache": "3.0.4",
"passport": "^0.6.0",
"express": "^4.19.2",
"express-rate-limit": "^7.2.0",
"form-data": "^4.0.0",
"helmet": "^7.1.0",
"ibm-watson": "^9.0.1",
"jsonwebtoken": "^9.0.2",
"mailgun.js": "^10.2.1",
"microsoft-cognitiveservices-speech-sdk": "1.36.0",
"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": "^5.17.0",
"short-uuid": "^4.1.0",
"stripe": "^8.222.0",
"swagger-ui-express": "^4.4.0",
"uuid": "^8.3.2",
"yamljs": "^0.3.0",
"ws": "^8.12.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",
"uuid": "^9.0.1",
"wav": "^1.0.2",
"@google-cloud/storage": "^6.12.0",
"@azure/storage-blob": "^12.15.0"
"ws": "^8.16.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"eslint": "^8.39.0",
"eslint-plugin-promise": "^6.1.1",
"husky": "7.0.4",
"husky": "9.0.11",
"nyc": "^15.1.0",
"request": "^2.88.2",
"request-promise-native": "^1.0.9",
"tape": "^5.5.3"
"tape": "^5.7.5"
}
}

View File

@@ -14,7 +14,7 @@ const {
createPhoneNumber,
deleteObjectBySid} = require('./utils');
const logger = require('../lib/logger');
const { addToSortedSet } = require('@jambonz/realtimedb-helpers')({
const { addToSortedSet, createHash } = require('@jambonz/realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
@@ -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`, {
@@ -314,6 +317,19 @@ test('account tests', async(t) => {
});
t.ok(result.statusCode === 200 && result.body.length === 0, 'successfully queried account queue info with for an invalid account');
// query conferences
await createHash(`conf:${sid}:conf1`, 'url1');
await createHash(`conf:${sid}:conf2`, 'url2');
await createHash(`conf:${sid}:conf3`, 'url3');
await createHash(`conf:${sid}:conf4`, 'url4');
result = await request.get(`/Accounts/${sid}/Conferences`, {
auth: authAdmin,
resolveWithFullResponse: true,
json: true,
});
t.ok(result.statusCode === 200 && result.body.length === 4, 'successfully queried account conferences info for an account');
/* delete account */
result = await request.delete(`/Accounts/${sid}`, {
auth: authAdmin,

View File

@@ -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}`, {

View File

@@ -12,6 +12,7 @@ process.on('unhandledRejection', (reason, p) => {
test('client test', async(t) => {
const app = require('../app');
const {registrar} = app.locals;
try {
let result;
@@ -35,6 +36,7 @@ test('client test', async(t) => {
body: {
name: 'sample_account',
service_provider_sid: sp_sid,
sip_realm: 'drachtio.org',
registration_hook: {
url: 'http://example.com/reg',
method: 'get'
@@ -54,12 +56,53 @@ test('client test', async(t) => {
account_sid,
username: 'client1',
password: 'sdf12412',
is_active: 1
is_active: 1,
allow_direct_app_calling: 0,
allow_direct_queue_calling: 1,
allow_direct_user_calling: 1,
}
});
t.ok(result.statusCode === 201, 'successfully created Client');
const sid = result.body.sid;
/* register the client */
const r = await registrar.add(
"dhorton@drachtio.org",
{
contact: "10.10.1.1",
sbcAddress: "192.168.1.1",
protocol: "udp",
},
5
);
t.ok(r, 'successfully registered Client');
/* query all registered clients */
result = await request.get(`/Accounts/${account_sid}/RegisteredSipUsers`, {
auth: authAdmin,
json: true,
});
t.ok(result.length === 1 && result[0] === 'dhorton@drachtio.org',
'successfully queried all registered clients');
result = await request.post(`/Accounts/${account_sid}/RegisteredSipUsers`, {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: ['dhorton']
});
t.ok(result.body.length === 1 && result.body[0].name === 'dhorton',
'successfully queried all registered clients');
result = await request.post(`/Accounts/${account_sid}/RegisteredSipUsers`, {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: []
});
t.ok(result.body.length === 1 && result.body[0].name === 'dhorton',
'successfully queried all registered clients');
/* query all entity */
result = await request.get('/Clients', {
auth: authAdmin,
@@ -77,6 +120,7 @@ test('client test', async(t) => {
t.ok(result.username === 'client1', 'successfully retrieved Client by sid');
t.ok(result.is_active === 1 , 'successfully retrieved Client by sid');
t.ok(result.password === 'sXXXXXXX' , 'successfully retrieved Client by sid');
t.ok(result.allow_direct_app_calling === 0 , 'successfully retrieved Client by sid');
/* update the entity */
result = await request.put(`/Clients/${sid}`, {

View File

@@ -32,3 +32,11 @@ test('add predefined carriers', (t) => {
t.end();
});
});
test('prepare permissions', (t) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 -D jambones_test < ${__dirname}/../db/prepare-permissions-test.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('permissions prepared');
t.end();
});
});

View File

@@ -10,7 +10,7 @@ networks:
services:
mysql:
platform: linux/x86_64
# platform: linux/x86_64
image: mysql:5.7
ports:
- "3360:3306"
@@ -36,7 +36,7 @@ services:
ipv4_address: 172.58.0.3
influxdb:
platform: linux/x86_64
# platform: linux/x86_64
image: influxdb:1.8
ports:
- "8086:8086"

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

View File

@@ -13,6 +13,7 @@ require('./ms-teams');
require('./speech-credentials');
require('./recent-calls');
require('./users');
require('./users-view-only');
require('./login');
require('./webapp_tests');
// require('./homer');

View File

@@ -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,
@@ -75,6 +122,44 @@ test('sip gateway tests', async(t) => {
});
//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,
auth: authAdmin,
json: true,
body: {
voip_carrier_sid,
ipv4: '192.168.1.2',
netmask: 32,
inbound: true,
outbound: true,
protocol: 'tls',
use_sips_scheme: true
}
});
t.ok(result.statusCode === 201, 'successfully created sip gateway ');
const sipsSid = result.body.sid;
/* query one sip gateway */
result = await request.get(`/SipGateways/${sipsSid}`, {
auth: authAdmin,
json: true,
});
//console.log(`result: ${JSON.stringify(result)}`);
t.ok(result.ipv4 === '192.168.1.2' , 'successfully retrieved voip carrier by sid');
t.ok(result.protocol === 'tls' , 'successfully retrieved voip carrier by sid');
t.ok(result.use_sips_scheme, 'successfully retrieved voip carrier by sid');
/* delete sip gateways */
result = await request.delete(`/SipGateways/${sipsSid}`, {
resolveWithFullResponse: true,
simple: false,
json: true,
auth: authAdmin
});
//console.log(`result: ${JSON.stringify(result)}`);
t.ok(result.statusCode === 204, 'successfully deleted sip gateway');
await deleteObjectBySid(request, '/VoipCarriers', voip_carrier_sid);

View File

@@ -170,6 +170,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for google tts');
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for google stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "en-US-Standard-C"
}
});
t.ok(result.statusCode === 200, 'successfully google tested synthesize');
}
/* add / test a credential for microsoft */
@@ -198,6 +212,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for microsoft tts');
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for microsoft stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "en-US-AvaMultilingualNeural"
}
});
t.ok(result.statusCode === 200, 'successfully microsoft tested synthesize');
}
/* add / test a credential for AWS */
@@ -227,6 +255,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for AWS tts');
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for AWS stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "Joanna"
}
});
t.ok(result.statusCode === 200, 'successfully AWS tested synthesize');
}
/* add a credential for wellsaid */
@@ -253,6 +295,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for wellsaid');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "3"
}
});
t.ok(result.statusCode === 200, 'successfully Wellsaid tested synthesize');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
@@ -285,6 +341,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for deepgram');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "aura-asteria-en"
}
});
t.ok(result.statusCode === 200, 'successfully deepgram tested synthesize');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
@@ -292,6 +362,64 @@ test('speech credentials tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
}
// test create deepgram onprem
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'deepgram',
use_for_stt: true,
deepgram_stt_uri: "127.0.0.1:50002",
deepgram_stt_use_tls: true,
deepgram_tts_uri: 'https://server.com'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for deepgram');
const dg_sid = result.body.sid;
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${dg_sid}`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
});
//console.log(JSON.stringify(result));
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,
auth: authUser,
json: true,
body: {
vendor: 'deepgram',
use_for_stt: true,
deepgram_stt_uri: "127.0.0.2:50002",
deepgram_stt_use_tls: false,
deepgram_tts_uri: 'https://server2.com'
}
});
t.ok(result.statusCode === 204, 'successfully updated speech credential for deepgram onprem');
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${dg_sid}`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
});
//console.log(JSON.stringify(result));
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,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for deepgram onprem');
/* add a credential for ibm tts */
if (process.env.IBM_TTS_API_KEY && process.env.IBM_TTS_REGION) {
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
@@ -350,6 +478,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for ibm stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "en-US_MichaelExpressive"
}
});
t.ok(result.statusCode === 200, 'successfully IBM tested synthesize');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
@@ -390,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,
@@ -447,6 +622,396 @@ test('speech credentials tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
/* add a credential for cobalt */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'cobalt',
use_for_stt: true,
use_for_tts: false,
cobalt_server_uri: '32.32.32.32:2727',
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for Cobalt');
const cobalt_sid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${cobalt_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for Cobalt');
/* add a credential for elevenlabs */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'elevenlabs',
use_for_stt: true,
use_for_tts: false,
api_key: 'asdasdasdasddsadasda',
model_id: 'eleven_multilingual_v2'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for elevenlabs');
const elevenlabs_sid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${elevenlabs_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for elevenlabs');
/* add a credential for playht */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'playht',
use_for_stt: false,
use_for_tts: true,
api_key: 'asdasdasdasddsadasda',
user_id: 'user_id',
voice_engine: 'PlayHT2.0-turbo'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for playht');
const playht_sid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${playht_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for playht');
/* add a credential for rimelabs */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'rimelabs',
use_for_stt: false,
use_for_tts: true,
api_key: 'asdasdasdasddsadasda',
model_id: 'mist',
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for rimelabs');
const rimelabs_sid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${rimelabs_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for rimelabs');
/* add a credential for custom voices google */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'google',
use_for_stt: true,
use_for_tts: false,
service_key: jsonKey
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for custom voice google');
const customvoice_google_speech_credential_sid = result.body.sid;
result = await request.post(`/GoogleCustomVoices`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: customvoice_google_speech_credential_sid,
name: "Sally",
reported_usage: 'REALTIME',
model: "path/to/modelId"
}
});
t.ok(result.statusCode === 201, 'successfully added custom voice google');
const customvoice_google_sid = result.body.sid;
/* delete the credential */
result = await request.delete(`/GoogleCustomVoices/${customvoice_google_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted custom voice google');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${customvoice_google_speech_credential_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for custom voice google');
/* add a credential for assemblyAI */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'assemblyai',
use_for_stt: true,
api_key: "APIKEY"
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for assemblyai');
const assemblyAiSid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${assemblyAiSid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
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,
auth: authUser,
json: true,
body: {
vendor: 'aws',
label: 'aws_polly_with_arn',
use_for_tts: true,
use_for_stt: false,
role_arn: 'Arn::aws::role',
aws_region: 'us-east-1'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for AWS Polly By RoleArn');
const awsPollySid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${awsPollySid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
/* add a credential for verbio */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'verbio',
use_for_tts: true,
use_for_stt: true,
client_id: 'client:id',
client_secret: 'client:secret',
engine_version: 'V1'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for Verbio');
const verbioSid = result.body.sid;
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.engine_version === "V1", 'successfully get verbio speech credential');
result = await request.put(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
use_for_tts: true,
use_for_stt: true,
engine_version: 'V2'
}
});
t.ok(result.statusCode === 204, 'successfully updated speech credential for verbio');
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.engine_version === "V2", 'successfully Updated verbio speech credential');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
/* Check google supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=google`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get google supported languages and voices');
t.ok(result.body.stt.length !== 0, 'successfully get google supported languages and voices');
/* Check aws supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=aws`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get aws supported languages and voices');
t.ok(result.body.stt.length !== 0, 'successfully get aws supported languages and voices');
/* Check microsoft supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=microsoft`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get microsoft supported languages and voices');
t.ok(result.body.stt.length !== 0, 'successfully get microsoft supported languages and voices');
/* Check wellsaid supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=wellsaid`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get wellsaid supported languages and voices');
/* Check nuance supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=nuance`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get nuance supported languages and voices');
t.ok(result.body.stt.length !== 0, 'successfully get nuance supported languages and voices');
/* Check deepgram supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=deepgram`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.stt.length !== 0, 'successfully get deepgram supported languages and voices');
t.ok(result.body.models.length !== 0, 'successfully get deepgram supported languages and voices');
/* Check ibm supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=ibm`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get ibm supported languages and voices');
t.ok(result.body.stt.length !== 0, 'successfully get ibm supported languages and voices');
/* Check ibm supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=nvidia`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get nvidia supported languages and voices');
t.ok(result.body.stt.length !== 0, 'successfully get nvidia supported languages and voices');
/* Check cobalt supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=cobalt`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.stt.length !== 0, 'successfully get cobalt supported languages and voices');
/* Check soniox supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=soniox`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.stt.length !== 0, 'successfully get soniox supported languages and voices');
/* Check elevenlabs supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=elevenlabs`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get elevenlabs supported languages and voices');
t.ok(result.body.models.length !== 0, 'successfully get elevenlabs supported languages and voices');
/* Check assemblyai supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=assemblyai`, {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
});
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,
simple: false,
auth: authAdmin,
json: true,
});
t.ok(result.body.tts.length !== 0, 'successfully get whisper supported languages and voices');
t.ok(result.body.models.length !== 0, 'successfully get whisper supported languages and voices');
await deleteObjectBySid(request, '/Accounts', account_sid);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
t.end();

View File

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

Some files were not shown because too many files have changed in this diff Show More