This commit is contained in:
Quan HL
2025-09-29 14:07:11 +07:00
13 changed files with 2409 additions and 3167 deletions

View File

@@ -174,5 +174,61 @@
"non è raggiungibile", "non è raggiungibile",
"lascia pure un messaggio", "lascia pure un messaggio",
"puoi lasciare un messaggio" "puoi lasciare un messaggio"
],
"ja-JP": [
"この通話は留守番電話に転送されました",
"発信先は現在電話に出ることができません",
"発信音の後でメッセージを録音してください",
"録音を完了したら電話を切ることができます",
"只今電話に出ることができません",
"ただ今電話に出ることができません",
"ただいま電話に出ることができません",
"ピーという発信音の後にお名前とご用件をお話しください",
"ファックスを送られる方はスタートボタンを押してください",
"FAXを送られる方はスタートボタンを押してください",
"おかけになった電話をお呼びしましたが",
"お出になりません",
"おでになりません",
"お掛けになった電話番号は",
"おかけになった電話番号は",
"お掛けになった電話は",
"おかけになった電話は",
"現在使われておりません",
"番号をお確かめになって",
"お掛け直し下さい",
"おかけ直し下さい",
"おかけ直しください",
"こちらはNTTドコモです",
"こちらはエーユーです",
"こちらはソフトバンクです",
"電波の届かない",
"電源が入っていない",
"掛かりません",
"かかりません",
"お繋ぎすることが出来ません",
"お繋ぎ出来ません",
"お繋ぎすることができません",
"お繋ぎできません",
"おつなぎすることができません",
"おつなぎできません",
"メッセージを録音",
"留守番電話",
"お留守番サービス",
"留守番",
"留守電",
"留守",
"接続します",
"合図の音",
"ピーと",
"発信音",
"ご用件",
"伝言",
"お話しください",
"ファックス",
"FAX",
"終了",
"終了しました",
"終了いたしました",
"営業時間"
] ]
} }

View File

@@ -147,7 +147,7 @@ router.post('/',
// find handling sbc sip for called user // find handling sbc sip for called user
if (JAMBONES_DIAL_SBC_FOR_REGISTERED_USER && target.type === 'user') { if (JAMBONES_DIAL_SBC_FOR_REGISTERED_USER && target.type === 'user') {
const { registrar } = srf.locals.dbHelpers; const { registrar} = srf.locals.dbHelpers;
const reg = await registrar.query(target.name); const reg = await registrar.query(target.name);
if (reg) { if (reg) {
sbcAddress = selectHostPort(logger, reg.sbcAddress, 'tcp')[1]; sbcAddress = selectHostPort(logger, reg.sbcAddress, 'tcp')[1];
@@ -159,7 +159,9 @@ router.post('/',
* trunk isn't specified, * trunk isn't specified,
* check if from-number matches any existing numbers on Jambonz * check if from-number matches any existing numbers on Jambonz
* */ * */
if (target.type === 'phone' && !target.trunk) { const { lookupLcrByAccount} = srf.locals.dbHelpers;
const lcrs = await lookupLcrByAccount(req.body.account_sid);
if (target.type === 'phone' && !target.trunk && lcrs.length == 0) {
const str = restDial.from || ''; const str = restDial.from || '';
const callingNumber = str.startsWith('+') ? str.substring(1) : str; const callingNumber = str.startsWith('+') ? str.substring(1) : str;
const voip_carrier_sid = await lookupCarrierByPhoneNumber(req.body.account_sid, callingNumber); const voip_carrier_sid = await lookupCarrierByPhoneNumber(req.body.account_sid, callingNumber);

View File

@@ -1415,7 +1415,11 @@ class CallSession extends Emitter {
} }
else { else {
if (this.req && !this.dlg) { if (this.req && !this.dlg) {
this.req.cancel(); try {
this.req.cancel();
} catch (err) {
this.logger.error({err}, 'CallSession:_lccCallStatus error cancelling request');
}
this._callReleased(); this._callReleased();
} }
} }
@@ -1862,7 +1866,7 @@ Duration=${duration} `
return; return;
} }
else if (tokens === undefined) { else if (tokens === undefined) {
this.logger.info({opts}, 'CallSession:_lccTtsTokens - invalid command since id is missing'); this.logger.info({opts}, 'CallSession:_lccTtsTokens - invalid command since tokens is missing');
return this.requestor.request('tts:tokens-result', '/tokens-result', { return this.requestor.request('tts:tokens-result', '/tokens-result', {
id, id,
status: 'failed', status: 'failed',
@@ -2681,7 +2685,7 @@ Duration=${duration} `
*/ */
_onRefer(req, res) { _onRefer(req, res) {
const task = this.currentTask; const task = this.currentTask;
const sd = task.sd; const sd = task?.sd;
if (task && TaskName.Dial === task.name && sd && task.referHook) { if (task && TaskName.Dial === task.name && sd && task.referHook) {
task.handleRefer(this, req, res); task.handleRefer(this, req, res);
} }

View File

@@ -641,7 +641,9 @@ class TaskDial extends Task {
* trunk isn't specified, * trunk isn't specified,
* check if number matches any existing numbers * check if number matches any existing numbers
* */ * */
if (t.type === 'phone' && !t.trunk) { const { lookupLcrByAccount} = srf.locals.dbHelpers;
const lcrs = await lookupLcrByAccount(cs.accountSid);
if (t.type === 'phone' && !t.trunk && lcrs.length == 0) {
const str = this.callerId || req.callingNumber || ''; const str = this.callerId || req.callingNumber || '';
const callingNumber = str.startsWith('+') ? str.substring(1) : str; const callingNumber = str.startsWith('+') ? str.substring(1) : str;
const voip_carrier_sid = await lookupCarrierByPhoneNumber(cs.accountSid, callingNumber); const voip_carrier_sid = await lookupCarrierByPhoneNumber(cs.accountSid, callingNumber);
@@ -674,7 +676,8 @@ class TaskDial extends Task {
rootSpan: cs.rootSpan, rootSpan: cs.rootSpan,
startSpan: this.startSpan.bind(this), startSpan: this.startSpan.bind(this),
dialTask: this, dialTask: this,
onHoldMusic: this.cs.onHoldMusic onHoldMusic: this.cs.onHoldMusic,
tmpFiles: this.cs.tmpFiles,
}); });
this.dials.set(sd.callSid, sd); this.dials.set(sd.callSid, sd);
@@ -775,6 +778,9 @@ class TaskDial extends Task {
this.epOther.api('uuid_break', this.epOther.uuid); this.epOther.api('uuid_break', this.epOther.uuid);
this.epOther.bridge(sd.ep); this.epOther.bridge(sd.ep);
} }
else {
this.logger.error('Dial:_connectSingleDial - no other endpoint to bridge!');
}
this.bridged = true; this.bridged = true;
} }

View File

@@ -395,6 +395,7 @@ class TaskGather extends SttTask {
if (this.digitBuffer.length === 0 && this.needsStt) { if (this.digitBuffer.length === 0 && this.needsStt) {
// DTMF is higher priority than STT. // DTMF is higher priority than STT.
this.removeCustomEventListeners(); this.removeCustomEventListeners();
this._clearAsrTimer(); //clear ASR timer as we're now using dtmf
this._stopTranscribing(ep); this._stopTranscribing(ep);
} }
this.digitBuffer += evt.dtmf; this.digitBuffer += evt.dtmf;
@@ -409,6 +410,7 @@ class TaskGather extends SttTask {
const ms = this.interDigitTimeout * 1000; const ms = this.interDigitTimeout * 1000;
this.logger.debug(`starting interdigit timer of ${ms}`); this.logger.debug(`starting interdigit timer of ${ms}`);
this.interDigitTimer = setTimeout(() => this._resolve('dtmf-interdigit-timeout'), ms); this.interDigitTimer = setTimeout(() => this._resolve('dtmf-interdigit-timeout'), ms);
this._clearTimer(); //clear main timer as we're now using interdigit dtmf timer
} }
} }
@@ -466,6 +468,7 @@ class TaskGather extends SttTask {
this.addCustomEventListener(ep, DeepgramTranscriptionEvents.Connect, this._onVendorConnect.bind(this, cs, ep)); this.addCustomEventListener(ep, DeepgramTranscriptionEvents.Connect, this._onVendorConnect.bind(this, cs, ep));
this.addCustomEventListener(ep, DeepgramTranscriptionEvents.ConnectFailure, this.addCustomEventListener(ep, DeepgramTranscriptionEvents.ConnectFailure,
this._onVendorConnectFailure.bind(this, cs, ep)); this._onVendorConnectFailure.bind(this, cs, ep));
this.addCustomEventListener(ep, DeepgramTranscriptionEvents.Error, this._onVendorError.bind(this, cs, ep));
break; break;
case 'deepgramriver': case 'deepgramriver':
@@ -476,6 +479,7 @@ class TaskGather extends SttTask {
ep, DeepgramRiverTranscriptionEvents.Connect, this._onVendorConnect.bind(this, cs, ep)); ep, DeepgramRiverTranscriptionEvents.Connect, this._onVendorConnect.bind(this, cs, ep));
this.addCustomEventListener(ep, DeepgramRiverTranscriptionEvents.ConnectFailure, this.addCustomEventListener(ep, DeepgramRiverTranscriptionEvents.ConnectFailure,
this._onVendorConnectFailure.bind(this, cs, ep)); this._onVendorConnectFailure.bind(this, cs, ep));
this.addCustomEventListener(ep, DeepgramRiverTranscriptionEvents.Error, this._onVendorError.bind(this, cs, ep));
break; break;
case 'soniox': case 'soniox':
@@ -702,8 +706,8 @@ class TaskGather extends SttTask {
this.logger.debug(`Starting timoutTimer of ${this.timeout}ms`); this.logger.debug(`Starting timoutTimer of ${this.timeout}ms`);
this._clearTimer(); this._clearTimer();
this._timeoutTimer = setTimeout(() => { this._timeoutTimer = setTimeout(() => {
// If continuousASR in use then extend by the asr window for more transcripts.
if (this.isContinuousAsr) this._startAsrTimer(); if (this.isContinuousAsr) this._startAsrTimer();
if (this.interDigitTimer) return; // let the inter-digit timer complete
else { else {
this._resolve(this.digitBuffer.length >= this.minDigits ? 'dtmf-num-digits' : 'timeout'); this._resolve(this.digitBuffer.length >= this.minDigits ? 'dtmf-num-digits' : 'timeout');
} }

View File

@@ -19,6 +19,7 @@ class TaskRestDial extends Task {
this.timeout = this.data.timeout || 60; this.timeout = this.data.timeout || 60;
this.sipRequestWithinDialogHook = this.data.sipRequestWithinDialogHook; this.sipRequestWithinDialogHook = this.data.sipRequestWithinDialogHook;
this.referHook = this.data.referHook; this.referHook = this.data.referHook;
this.recentCallStatus = 0;
this.on('connect', this._onConnect.bind(this)); this.on('connect', this._onConnect.bind(this));
this.on('callStatus', this._onCallStatus.bind(this)); this.on('callStatus', this._onCallStatus.bind(this));
@@ -57,7 +58,11 @@ class TaskRestDial extends Task {
this._clearCallTimer(); this._clearCallTimer();
if (this.canCancel) { if (this.canCancel) {
this.canCancel = false; this.canCancel = false;
cs?.req?.cancel(); try {
cs?.req?.cancel();
} catch (err) {
this.logger.error({err}, 'TaskRestDial: error cancelling call');
}
} }
this.notifyTaskDone(); this.notifyTaskDone();
} }
@@ -118,7 +123,8 @@ class TaskRestDial extends Task {
} }
_onCallStatus(status) { _onCallStatus(status) {
this.logger.debug(`CallStatus: ${status}`); this.logger.debug(`RestDial CallStatus: ${status}`);
this.recentCallStatus = status;
if (status >= 200) { if (status >= 200) {
this.canCancel = false; this.canCancel = false;
this._clearCallTimer(); this._clearCallTimer();
@@ -136,11 +142,16 @@ class TaskRestDial extends Task {
} }
_onCallTimeout() { _onCallTimeout() {
this.logger.debug('TaskRestDial: timeout expired without answer, killing task'); this.logger.debug(`TaskRestDial: timeout expired without answer, last status ${this.recentCallStatus}`);
this.timer = null; this.timer = null;
if (this.canCancel) { if (this.canCancel && this.recentCallStatus < 200) {
this.logger.debug('TaskRestDial: cancelling call attempt');
this.canCancel = false; this.canCancel = false;
this.cs?.req?.cancel(); try {
this.cs?.req?.cancel();
} catch (err) {
this.logger.error({err}, 'TaskRestDial: error cancelling call');
}
} }
} }

View File

@@ -237,6 +237,8 @@ class TaskTranscribe extends SttTask {
this._onVendorConnect.bind(this, cs, ep)); this._onVendorConnect.bind(this, cs, ep));
this.addCustomEventListener(ep, DeepgramTranscriptionEvents.ConnectFailure, this.addCustomEventListener(ep, DeepgramTranscriptionEvents.ConnectFailure,
this._onVendorConnectFailure.bind(this, cs, ep, channel)); this._onVendorConnectFailure.bind(this, cs, ep, channel));
this.addCustomEventListener(ep, DeepgramTranscriptionEvents.Error, this._onVendorError.bind(this, cs, ep));
/* if app sets deepgramOptions.utteranceEndMs they essentially want continuous asr */ /* if app sets deepgramOptions.utteranceEndMs they essentially want continuous asr */
//if (opts.DEEPGRAM_SPEECH_UTTERANCE_END_MS) this.isContinuousAsr = true; //if (opts.DEEPGRAM_SPEECH_UTTERANCE_END_MS) this.isContinuousAsr = true;
@@ -250,6 +252,8 @@ class TaskTranscribe extends SttTask {
this._onVendorConnect.bind(this, cs, ep)); this._onVendorConnect.bind(this, cs, ep));
this.addCustomEventListener(ep, DeepgramRiverTranscriptionEvents.ConnectFailure, this.addCustomEventListener(ep, DeepgramRiverTranscriptionEvents.ConnectFailure,
this._onVendorConnectFailure.bind(this, cs, ep, channel)); this._onVendorConnectFailure.bind(this, cs, ep, channel));
this.addCustomEventListener(ep, DeepgramRiverTranscriptionEvents.Error, this._onVendorError.bind(this, cs, ep));
break; break;
case 'soniox': case 'soniox':
this.bugname = `${this.bugname_prefix}soniox_transcribe`; this.bugname = `${this.bugname_prefix}soniox_transcribe`;

View File

@@ -84,8 +84,7 @@ class TtsTask extends Task {
const {api_key, model_id, custom_tts_streaming_url, auth_token} = credentials; const {api_key, model_id, custom_tts_streaming_url, auth_token} = credentials;
let obj; let obj;
this.logger.debug({credentials}, this.logger.debug(`setTtsStreamingChannelVars: vendor: ${vendor}, language: ${language}, voice: ${voice}`);
`setTtsStreamingChannelVars: vendor: ${vendor}, language: ${language}, voice: ${voice}`);
switch (vendor) { switch (vendor) {
case 'deepgram': case 'deepgram':
@@ -328,6 +327,13 @@ class TtsTask extends Task {
this.playbackIds.push(extractPlaybackId(filePath)); this.playbackIds.push(extractPlaybackId(filePath));
this.logger.debug({playbackIds: this.playbackIds}, 'Say: a streaming tts api will be used'); this.logger.debug({playbackIds: this.playbackIds}, 'Say: a streaming tts api will be used');
const modifiedPath = filePath.replace('say:{', `say:{session-uuid=${ep.uuid},`); const modifiedPath = filePath.replace('say:{', `say:{session-uuid=${ep.uuid},`);
this.notifyStatus({
event: 'synthesized-audio',
vendor,
language,
servedFromCache,
'id': this.id
});
return modifiedPath; return modifiedPath;
} }
return filePath; return filePath;

View File

@@ -94,12 +94,14 @@
"DeepgramTranscriptionEvents": { "DeepgramTranscriptionEvents": {
"Transcription": "deepgram_transcribe::transcription", "Transcription": "deepgram_transcribe::transcription",
"ConnectFailure": "deepgram_transcribe::connect_failed", "ConnectFailure": "deepgram_transcribe::connect_failed",
"Connect": "deepgram_transcribe::connect" "Connect": "deepgram_transcribe::connect",
"Error": "deepgram_transcribe::error"
}, },
"DeepgramRiverTranscriptionEvents": { "DeepgramRiverTranscriptionEvents": {
"Transcription": "deepgramriver_transcribe::transcription", "Transcription": "deepgramriver_transcribe::transcription",
"ConnectFailure": "deepgramriver_transcribe::connect_failed", "ConnectFailure": "deepgramriver_transcribe::connect_failed",
"Connect": "deepgramriver_transcribe::connect" "Connect": "deepgramriver_transcribe::connect",
"Error": "deepgramriver_transcribe::error"
}, },
"SonioxTranscriptionEvents": { "SonioxTranscriptionEvents": {
"Transcription": "soniox_transcribe::transcription", "Transcription": "soniox_transcribe::transcription",

View File

@@ -173,7 +173,8 @@ function installSrfLocals(srf, logger, {
lookupAccountCapacitiesBySid, lookupAccountCapacitiesBySid,
lookupSmppGateways, lookupSmppGateways,
lookupClientByAccountAndUsername, lookupClientByAccountAndUsername,
lookupSystemInformation lookupSystemInformation,
lookupLcrByAccount
} = require('@jambonz/db-helpers')({ } = require('@jambonz/db-helpers')({
host: JAMBONES_MYSQL_HOST, host: JAMBONES_MYSQL_HOST,
user: JAMBONES_MYSQL_USER, user: JAMBONES_MYSQL_USER,
@@ -279,7 +280,8 @@ function installSrfLocals(srf, logger, {
retrieveByPatternSortedSet, retrieveByPatternSortedSet,
sortedSetLength, sortedSetLength,
sortedSetPositionByPattern, sortedSetPositionByPattern,
getVerbioAccessToken getVerbioAccessToken,
lookupLcrByAccount
}, },
parentLogger: logger, parentLogger: logger,
getSBC, getSBC,

View File

@@ -20,7 +20,7 @@ const { createMediaEndpoint } = require('./media-endpoint');
class SingleDialer extends Emitter { class SingleDialer extends Emitter {
constructor({logger, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask, constructor({logger, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask,
onHoldMusic}) { onHoldMusic, tmpFiles}) {
super(); super();
assert(target.type); assert(target.type);
@@ -44,6 +44,7 @@ class SingleDialer extends Emitter {
this.callSid = crypto.randomUUID(); this.callSid = crypto.randomUUID();
this.dialTask = dialTask; this.dialTask = dialTask;
this.onHoldMusic = onHoldMusic; this.onHoldMusic = onHoldMusic;
this.tmpFiles = tmpFiles;
this.on('callStatusChange', this._notifyCallStatusChange.bind(this)); this.on('callStatusChange', this._notifyCallStatusChange.bind(this));
} }
@@ -328,7 +329,13 @@ class SingleDialer extends Emitter {
*/ */
async kill(Reason) { async kill(Reason) {
this.killed = true; this.killed = true;
if (this.inviteInProgress) await this.inviteInProgress.cancel(); if (this.inviteInProgress) {
try {
await this.inviteInProgress.cancel();
} catch (err) {
this.logger.error({err}, 'SingleDialer:kill error cancelling invite');
}
}
else if (this.dlg && this.dlg.connected) { else if (this.dlg && this.dlg.connected) {
const duration = moment().diff(this.dlg.connectTime, 'seconds'); const duration = moment().diff(this.dlg.connectTime, 'seconds');
this.logger.debug('SingleDialer:kill hanging up called party'); this.logger.debug('SingleDialer:kill hanging up called party');
@@ -402,7 +409,7 @@ class SingleDialer extends Emitter {
tasks, tasks,
rootSpan: this.rootSpan, rootSpan: this.rootSpan,
req: this.req, req: this.req,
tmpFiles: cs.tmpFiles, tmpFiles: this.tmpFiles,
}); });
await cs.exec(); await cs.exec();
@@ -536,12 +543,12 @@ class SingleDialer extends Emitter {
function placeOutdial({ function placeOutdial({
logger, srf, ms, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask, logger, srf, ms, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask,
onHoldMusic onHoldMusic, tmpFiles
}) { }) {
const myOpts = deepcopy(opts); const myOpts = deepcopy(opts);
const sd = new SingleDialer({ const sd = new SingleDialer({
logger, sbcAddress, target, opts: myOpts, application, callInfo, logger, sbcAddress, target, opts: myOpts, application, callInfo,
accountInfo, rootSpan, startSpan, dialTask, onHoldMusic accountInfo, rootSpan, startSpan, dialTask, onHoldMusic, tmpFiles
}); });
sd.exec(srf, ms, myOpts); sd.exec(srf, ms, myOpts);
return sd; return sd;

5420
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,11 +27,11 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-auto-scaling": "^3.549.0", "@aws-sdk/client-auto-scaling": "^3.549.0",
"@aws-sdk/client-sns": "^3.549.0", "@aws-sdk/client-sns": "^3.549.0",
"@jambonz/db-helpers": "^0.9.16", "@jambonz/db-helpers": "^0.9.17",
"@jambonz/http-health-check": "^0.0.1", "@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "^0.2.7", "@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.15", "@jambonz/realtimedb-helpers": "^0.8.15",
"@jambonz/speech-utils": "^0.2.22", "@jambonz/speech-utils": "^0.2.23",
"@jambonz/stats-collector": "^0.1.10", "@jambonz/stats-collector": "^0.1.10",
"@jambonz/time-series": "^0.2.14", "@jambonz/time-series": "^0.2.14",
"@jambonz/verb-specifications": "^0.0.113", "@jambonz/verb-specifications": "^0.0.113",