ספריה שמאפשרת לתכנת מערכות טלפוניות בקלות באמצעות מודול API של חברת 'ימות המשיח'.
מטרת הספריה לאפשר תקשורת מול המערכת הטלפונית בצורה נקיה וקריאה:
- הרצה רציפה של הקוד מתחילה ועד סוף, תוך שמירת הstate של השיחה בין הקריאות, בצורה שקופה לחלוטין
- יצירת התשובות על ידי קריאה למתודות של השיחה במקום יצירה ידנית של הסטרינגים
- אפשרויות נוחות נוספות כגון מטפל בשגיאות, הסרה של תווים לא חוקיים, לוג אוטומטי מפורט (אופציונלי)
- תיעוד מפורט מוטמע ומוצג תוך כדי עריכה (JSDoc)
- תמיכה בTypeScript
- ועוד אפשרויות רבות! פירוט בתיעוד 👇
npm install yemot-router2
⚙️ משדרגים מגרסה קודמת? ראו changelog!
מומלץ להשתמש בכפתור "תוכן העניינים" האוטומטי כדי לנווט בתיעוד:
הספריה עובדת על ידי חיקוי של Express Router, כך שאופן השימוש הוא די דומה. דוגמה:
import { YemotRouter } from 'yemot-router2';
const router = YemotRouter();
router.get('/', async (call) => {
return call.id_list_message([{
type: 'text',
data: 'שלום עולם'
}])
});
את הראוטר יש לחבר לאפליקציית express על ידי app.use
, כרגיל.
הראוטר מקבל פונקצייה אסינכרונית, שהארגומנט שהיא מקבלת מהראוטר הוא אובייקט Call
(יבואר בהמשך), המייצג את השיחה, ובאמצעות המתודות שלו ניתן לקבל מידע אודות השיחה, להשמיע/לבקש נתונים מהמחייג, להפנות אותו לשלוחה אחרת, ועוד.
👨💻 דוגמה בסיסית: example.js
טיפ: מומלץ מאוד לא להגדיר בשלוחה api_hangup_send=no
, לביצועי זיכרון טובים יותר.
הראוטר מקבל את הפונקציות הבאות, כולן אופציונליות:
- timeout: זמן המתנה לקבלת נתון מהמשתמש (במילישניות). במידה ולא התקבל הנתון בזמן הנ"ל, השיחה תימחק מהactiveCalls וגם אם המשתמש יקיש השרת יקבל אותו כשיחה חדשה.
מקבל מספר מילישיניות, או מחרוזת הקבילה ע"י ספריית ספריית ms.
מכסה גם מקרי קצה שלא התקבלה מימות הקריאה עם hangup=yes.
יש לשים לב לא להגדיר ערך נמוך שמחייג לגיטימי שלא הקיש מיד תשובה עלול להיתקל ב-timeout. - printLog (בוליאני): האם להדפיס לוג מפורט על כל קריאה לשרת, ניתוק שיחה וכולי. שימושי לפיתוח.
- uncaughtErrorHandler: (פונקציה) פונקציה לטיפול בשגיאות לא מטופלות בתוך שיחה, שימושי לדוגמה לשמירת לוג + השמעת הודעה למשתמש, במקום קריסה של השרת, והמשתמש ישמע "אין מענה משרת API". ראה דוגמה בexample.js.
בנוסף ניתן להעביר אופציות של אקספרס ראוטר עצמו - ראה פירוט בתיעוד express.js.
מתודה לקבלת נתון מהמחייג, מחזירה Promise
עם התשובה (במקרה של בקשת הקלטה, יחזור נתיב הקובץ).
פירוט נוסף על read
: https://f2.freeivr.co.il/post/78283
ההודעות שיושמעו למחייג לפני קבלת הנתון.
מערך של אובייקטי "הודעה" (ראה פירוט בהמשך), שיושמעו למשתמש ברצף.
מגדיר את סוג הנתון שמבקשים מהמחייג:
tap
= הקשות
stt
= זיהוי דיבור
record
= הקלטה
בפרמטר הזה, ניתן להעביר אפשרויות נוספות, כגון סך הקשות מינימלי, מקסימלי, וכו'.
let options = {
/* שם הערך בימות
ברירת מחדל, נקבע אוטומטית,
val_1, val_2, val_3 ...
*/
val_name: "val_x",
/* האם לבקש את הערך שוב אם קיים. */
re_enter_if_exists: false,
/* כמות הספרות המקסימלית שהמשתמש יוכל להקיש */
max_digits: "*",
/* כמות ספרות מינימלית */
min_digits: 1,
/* שניות להמתנה */
sec_wait: 7,
/* צורת ההשמעה למשתמש את הקשותיו
באם מעוניינים במקלדת שונה ממקלדת ספרות, כגון EmailKeyboard או HebrewKeyboard, יש להכניס כאן את סוג המקלדת
[ראו example.js]
האופציות הקיימות:
"Number" | "Digits" | "File" | "TTS" | "Alpha" | "No" | "HebrewKeyboard" |
"EmailKeyboard" | "EnglishKeyboard" | "DigitsKeyboard" | "TeudatZehut" |
"Price" | "Time" | "Phone" | "No"
פירוט על כל אופציה ניתן למצוא בתיעוד מודול API של ימות המשיח, תחת"הערך השישי (הקשה)".
*/
typing_playback_mode: "No",
/* האם לחסום הקשה על כוכבית */
block_asterisk_key: false,
/* האם לחסום הקשה על אפס */
block_zero_key: false,
/* החלפת תווים*/
replace_char: "",
/* ספרות מותרות להקשה - מערך
[1, 2, 3 ...]
*/
digits_allowed: [],
/* כמה פעמים להשמיע את השאלה לפני שליחת תשובת "None" (כלומר תשובה ריקה). ברירת מחדל פעם אחת */
amount_attempts: "",
/*
האם לאפשר תשובה ריקה - או שלאחר זמן ההמתנה יושמע "לא הוקשה בחירה" וידרוש להקיש
ברירת מחדל לא מאפשר תשובה ריקה
*/
allow_empty: false,
/*
הערך שיישלח כשלא הוקשה תשובה. ברירת מחדל "None"
ניתן להעביר גם ערכים שאינם מחרוזת, לדוגמה null והערך שיתקבל מהread יהיה null ולא 'null'
*/
empty_val: "None",
/* האם לחסום שינוי שפת מקלדת */
block_change_keyboard: false,
}
ערכי ברירת מחדל - הקלטות:
const options = {
/* נתיב לשמירת ההקלטה - שלוחה בלבד, ברירת מחדל שלוחה נוכחית, או api_dir אם מוגדר */
path: '',
/* שם קובץ (ללא סיומת) לשמירת ההקלטה, ברירת מחדל - ממוספר אוטומטית כקובץ הגבוה בשלוחה */
file_name: '',
/*
ברירת מחדל משמיע תפריט לאישור ההקלטה/הקלטה מחדש, ניתן להגדיר שמיד בהקשה על סולמית ההקלטה תאושר
*/
no_confirm_menu: false,
/* האם לשמור את ההקלטה באם המשתמש ניתק באמצע הקלטה */
save_on_hangup: false,
/*
במידה והוגדר שם קובץ לשמירה (file_name) וכבר קיים קובץ כזה,
האם לשנות את שם הקובץ הישן ולשמור את החדש בשם שנבחר (ברירת מחדל), או לצרף את ההקלטה החדשה לסוף הקובץ הישן
*/
append_to_existing_file: false,
/* כמות שניות מינימלית להקלטה, ברירת מחדל אין מינימום */
min_length: '',
/* כמות שניות מקסימלית להקלטה, ברירת מחדל ללא הגבלה */
max_length: ''
};
ערכי ברירת מחדל - זיהוי דיבור:
const options = {
/*
שפת הדיבור
ברירת מחדל עברית או מה שהוגדר בlang בשלוחה,
רשימת השפות הזמינות להגדרה: https://did.li/m1lrl
*/
lang: '',
/*
האם לחסום הקשה במצב זיהוי דיבור
ברירת מחדל מאפשר להקיש תוך כדי הדיבור, כלומר המחייג בוחר אם להקיש או לדבר
*/
block_typing: false,
/*
מקסימום ספרות שאפשר להקיש, באם לא נחסמה ההקשה תוך כדי דיבור
ברירת מחדל לא מוגבל
*/
max_digits: '',
/*
האם להשתמש במנוע הדיבור של הקלטות - נצרך עבור זיהוי טקסט ארוך
באם מפעילים הגדרה זו, לא ניתן לקלוט הקשות תוך כדי דיבור
*/
use_records_recognition_engine: false,
/*
אחרי כמה שניות של שקט לסיים את ההקלטה,
רלוונטי רק אם משתמשים במנוע זיהוי טקסטים ארוכים (use_records_recognition_engine)
*/
quiet_max: '',
/*
* מספר שניות מרבי להקלטה, ברירת מחדל: ללא הגבלה
*/
length_max: ''
};
מתודה להעברת השיחה לשלוחה מסוימת במערכת הנוכחית.
ניתן לכתוב נתיב יחסי לשלוחה הנוכחית או לשלוחה הראשית, פירוט על האופציות הזמינות ניתן לקרוא כאן.
ניתן גם להעביר בפרמטר folder את הסטרינג hangup
, וכך לנתק את השיחה, או להשתמש בקיצור call.hangup()
.
הפעלה מחדש של השלוחה הנוכחית.
קיצור לתחביר הבא:
call.go_to_folder(`/${call.ApiExtension}`);
ניתוק השיחה. קיצור לתחביר הבא:
call.go_to_folder('hangup');
במתודה זו ניתן להשמיע למשתמש הודעה אחת, או מספר הודעות ברצף.
המתודה מקבלת מערך של הודעות (אובייקט message) ומשמיעה אותן למשתמש.
לאחר השמעת ההודעות, השיחה תצא אוטומטית מהשלוחה!
באם מעוניינים לשרשר פעולה נוספת לאחר ההשמעה, לדוגמה להשמיע הודעה ואז לבצע read
(קבלת נתונים נוספים), יש להגדיר בארגומנט הoptions
את prependToNextAction
לtrue
.
מתודה להעברת השיחה למערכת אחרת בימות המשיח ללא עלות יחידות, באמצעות "ראוטינג ימות".
הפונקציה מקבלת ארגומנט יחיד - סטרינג של מספר מערכת בימות להעברת השיחה.
ניתן גם לנתב את השיחה ממערכת בשרת הפריווט למערכת בשרת הרגיל ולהיפך.
ניתן להשתמש במתודה זו כדי לשלוח סטרינג חופשי לחלוטין, לדוגמה עבור פונקציונליות שעדיין לא נתמכת בספרייה.
במתודה זו יש להעביר את הסטרינג בדיוק כפי שמעוניינים שהשרת של ימות יקבל אותו, והוא לא עובר ולידציה או עיבוד.
כדי להשתמש לבקשת מידע - לדוגמה מעבר לסליקת אשראי, יש לשלב עם קריאות ל
await call.blockRunningUntilNextRequest();
מכיל את כל הפרמטרים שנשלחו מימות -
אם הבקשה נשלחה ב-HTTP GET
, יכיל את הquery string
,
אם הבקשה נשלחה ב-HTTP POST
(api_url_post=yes
), יכיל את הbody
כל אובייקט הודעה צריך להיות במבנה הבא:
{ type: string, data: string }
כאשר type
הוא סוג הודעה מתוך הטבלה שלהלן, וdata
הוא המידע עצמו - string
,
מלבד כאשר הtype
הוא zmanim
/music_on_hold
, שאז הdata
יהיה אובייקט - מפורט מתחת לטבלה:
סוגי הודעות נתמכים:
סוג | תיאור מקוצר | דוגמה | הערות |
---|---|---|---|
text |
הקראת טקסט | דוגמה לטקסט דוגמה |
שים לב לאזהרה מתחת לטבלה לגבי תווים שלא ניתן להקריא |
file |
השמעת קובץ אודיו | /1/002 , ניתן לכתוב רק את שם הקובץ 002 אם הקובץ נמצא בתקייה הנוכחית |
אין לכתוב סיומת קובץ. ניתן להשמיע ממאגר גלובלי. |
speech |
הקראה אוטומטית של קובץ TTS | נתיב לקובץ TTS או שם קובץ TTS בתקיה הנוכחית | ללא הסיומת |
digits |
השמעת ספרות | 105 - ישמיע "אחת אפס חמש" |
שימושי בעיקר להקראת מספר טלפון |
number |
השמעת מספר | 105 - ישמיע "מאה וחמש" |
- |
alpha |
השמעת אותיות באנגלית | abc , ישמיע "איי, בי, סי" |
לא תומך בעברית |
zmanim |
השמעת שעה | אובייקט. פירוט בנפרד 👇 | - |
system_message |
השמעת הודעת מערכת | M1005 או 1005 |
רשימת הודעות המערכת |
music_on_hold |
השמעת מוזיקה בהמתנה | { musicName: 'ztomao', maxSec: 10 } |
הפרמור maxSec רשות. ראה כאן סוגי מוזיקה זמינים והוראות ליצירת חדש. |
date |
השמעת תאריך עברי | פורמט DD/MM/YYYY - 28/07/2022 |
- |
dateH |
השמעת תאריך לועזי | פורמט תאריך לועזי DD/MM/YYYY , ישמיע את התאריך העברי המתאים |
- |
go_to_folder |
נתיב יחסי לשלוחה הנוכחית או לשלוחה הראשית, ראה כאן | העברת השיחה לשלוחה אחרת | לא מומלץ, עדיף להשתמש ב call.go_to_folder . לא ניתן לשרשר הודעות נוספות לאחר סוג זה. |
לא ניתן להחזיר לימות את התוים:
נקודה,מקף,גרש,גרשיים,&
העברת אחד מהתוים הנ"ל יגרום לזריקת שגיאה, אלא אם כן נאפשר הסרת תווים לא חוקיים שקטה:
כאשר מעבירים טקסט להקראה ('type: 'text
) ניתן להגדיר הסרה של תווים לא חוקיים, כלומר שבמקום לזרוק שגיאה הם פשוט יוסרו מהתשובה שתוחזר לימות.
ההגדרה היא removeInvalidChars
, אותה ניתן להגדיר בשתי רמות, ברמת הודעה בודדת, או ברמת כל הread
/id_list_message
.
דוגמאות:
- ברמת ההודעה המסוימת - העברת הפרמטר
removeInvalidChars
באובייקט ההודעה:
{
type: "text",
data: "טקסט. בעייתי.",
removeInvalidChars: true
}
- ברמת כל ה
read
/id_list_message
- העברת הפרמטרremoveInvalidChars
באובייקט האפשרויות.
דוגמה לread
:
const resp = await call.read(messagesWidthInvalidChars, 'tap', { removeInvalidChars: true });
דוגמה לid_list_message
:
call.id_list_message(messagesWidthInvalidChars, { removeInvalidChars: true });
{
time: string, // optional, default: "T" (current time)
zone: string, // optional, default: "IL/Jerusalem",
difference: string // optional, default: 0
};
סוג הזמן שרוצים להשמיע.
ברירת מחדל: "T
" = השעה הנוכחית.
השמעת שעה - THH:MM
,
או זמן הלכתי - ניתן לראות כאן את רשימת הזמנים שניתן לחשב מהם זמן.
אזור הזמן שעבורו יש לחשב את הזמנים.
ברירת מחדל: IL/Jerusalem
.
ניתן לראות כאן את רשימת אזורי הזמן הקיימים במערכת.
ערך זה משמש להוספה/הסרה מלאכותית של זמן על הזמן שמשמיעים.
באם לא יועבר פרמטר זה, יושמע הזמן ללא שינוי.
הערך difference
מכיל קודם את סוג הפעולה - פלוס (+) להוספת זמן, או מינוס (-) להפחתת זמן, ואז את הזמן על פי הצורה הבאה: Y - שנה M - חודש D - יום H - שעה m - דקה S - שניה s - אלפית שניה למשל, עבור 20 דקות אחורה יש להגדיר m20-
, עבור 3 שעות קדימה יש לרשום H3+
. עבור יומיים אחורה יש לרשום D1-
.
לדוגמה, עבור השמעת זמן שקיעת החמה מחר בעיר בני ברק:
const messages = [{
type: 'zmanim',
data: {
time: 'sunset',
zone: 'IL/Bney_Brak',
difference: '+1D'
}
}];
{
musicName: string,
maxSec: number // optional
};
כאן סוגי מוזיקה זמינים והוראות ליצירת חדשה.
שימו לב:
אפשרות זו זמינה בגרסה 6.0 ומעלה
אפשרות זו הינה אופציונלית לחלוטין, ניתן להמשיך להעביר אובייקט אפשרויות בכל read
/id_list_message
כמו קודם
ניתן להגדיר ברירות מחדל בדרכים הבאות:
- ברירות מחדל של המערכת - שהן כמו ברירות המחדל של ימות (defaults.js)
- רמת ראוטר
- רמת שיחה
- ספציפית לקריאת
read
/id_list_message
מסויימת
האפשרויות ימוזגו עם סדר קדימויות. הסדר הוא: ברירות המחדל של הספרייה שנמצאת ב- lib/defaults.js, ברמת מופע ראוטר, ברמת מופע שיחה, ברמת קריאה ספציפית.
כל אפשרות מקבלת קדימות ודורסת את זו שלפניה.
דוגמה:
const router = YemotRouter({
printLog: true,
defaults: {
removeInvalidChars: true,
read: {
timeout: 30000
}
}
});
// אפשר גם כך:
// router.defaults.read.timeout = 30000;
router.get('/', async (call) => {
// הtimeout יהיה 30 שניות
await call.read([{ type: 'text', data: 'היי, תקיש 1' }], 'tap', {
max_digits: 1,
digits_allowed: [1]
});
// הtimeout יהיה 40 שניות
call.defaults.read.timeout = 40000;
await call.read([{ type: 'text', data: 'היי, תקיש 1' }], 'tap', {
max_digits: 1,
digits_allowed: [1]
});
// הtimeout יהיה 60 שניות
await call.read([{ type: 'text', data: 'היי, תקיש 1' }], 'tap', {
max_digits: 1,
digits_allowed: [1],
timeout: 60000
});
});
בדוגמה מאתחלים את הראוטר עם הגדרה של timeout של 1000 שניות, לאחר מכן בתוך השיחה משנים אותו ל2000, ולאחר מכן מבצעים קריאה בודדת עם אובייקט אופציות עם timeout של 3000, והוא גובר על ההגדרות הקודמות שהיו ברמה יותר גבוהה.
שימו לב! ניתן להגדיר את האופציות הבאות ברמת קריאה בודדת בלבד ולא ברמת שיחה/ראוטר:
val_name
(read
)prependToNextAction
(id_list_message
)
ניתן להאזין לאירועים ברמת ראוטר על ידי האזנה לrouter.events
:
new_call
- כאשר שיחה חדשה נכנסת למערכתcall_continue
- התקבלה תשובה לקריאת readcall_hangup
- כאשר שיחה מנותקת על ידי המחייג (התקבלה בקשה עםhangup=yes
)
שימושי לדוגמה כדי לטפל בקריאות עם hangup=yes
גם מחוץ לשלוחה.
הערות לשימוש בספריה עם טייפסקריפט:
בשימוש בראוטר עם אקספרס -
const router = YemotRouter()
app.use(router)
תוצג שגיאת טייפ. הפיתרון כרגע:
const { Router } = require('express')
const router = YemotRouter()
app.use(router.asExpressRouter)
כרגע הDTS לא מוגדר להסיק את הטייפ אוטומטית, ויש להגדיר אותו ידנית עם as
, דוגמה:
const res = await call.read([{ type: 'text', data: 'please type one' }], 'tap', {
allow_empty: true,
empty_val: null,
}) as string | null;
id_list_message
), ולאחר מכן להריץ קוד "כבד", ולא נצרכת אם נותנים למאזין להמתין לאישור ביצוע הפעולה (באמצעות read
).
בעת החזרת תשובה שאינה read
ולא גורמת לשרת של ימות לחזור לראוטר - id_list_message
או go_to_folder
,
נזרקת שגיאה על ידי הספריה - שתופסת אותה בחזרה ברמה יותר גבוהה, וכך ריצת הפונקציה נהרגת כדי לחסוך בזיכרון
וכן לנקות את השיחה מהactiveCalls
- כדי שבכניסה חוזרת לשלוחה הפונקציה תרוץ מההתחלה ולא תמשיך.
לכן אם מנסים להחזיר תשובה למשתמש ולאחר מכן להריץ את הקוד ה"כבד" כמו שעושים בשרת nodejs + express רגיל, לדוגמה:
function runBigJob(req, res) {
res.status(202).send('ok, we got your request, we will send you an email when the job is done')
doBigJob()
}
או במקרה של הראוטר - קוד (שגוי) כזה:
async function runBigJob(call) {
call.id_list_message([{
type: 'text',
data: 'בסדר, בקשתך תטופל בהקדם'
}])
await doBigJob()
}
קוד כזה לא יעבוד כיוון שהקריאה לcall.id_list_messsage
זורקת שגיאה שהורגת את ריצת הפונקציה, ולכן הקוד שלאחריה לא ירוץ.
כדי להריץ קוד לאחר החזרת התשובה, יש לתפוס את השגיאה שנזרקת (במקרה שהיא שגיאת ExitError
פנימית כנ"ל):
import { ExitError } from 'yemot-router2';
async function runBigJob (call) {
try {
call.id_list_message([{
type: 'text',
data: 'בסדר, בקשתך תטופל בהקדם'
}]);
} catch (error) {
if (error.isExitError) return;
throw error;
};
await doBigJob();
}
אין צורך לזרוק ידנית את ExitError
כדי למחוק את השיחה מהactiveCalls, כיוון שהיא נמחקת אוטומטית על ידי הספריה בסיום הריצה של הפונקציה.
עם זאת, באם הקוד הנוסף המורץ אמור לקחת זמן, מומלץ לנקות ידנית באופן מיידי (מייד לאחר החזרת התשובה בid_list_message
) את השיחה מהactiveCalls - אחרת לא יהיה ניתן להיכנס לשלוחה עד לסיום ביצוע הפעולה הכבדה:
const router = YemotRouter({ printLog: true });
router.get('/', async (call) => {
try {
call.id_list_message([{
type: 'text',
data: 'בסדר, בקשתך תטופל בהקדם'
}]);
} catch (error) {
if (error.isExitError) return;
throw error;
};
router.deleteCall(call.callId);
await doBigJob();
});
אופציה נוספת היא להריץ את הקוד בצורה מנותקת מהפונקציה, כלומר קריאה לקוד ה"כבד" בתוך פונקצייה אנונימית לפני החזרת התשובה:
(() => {
await doBigJob();
})();
call.id_list_message(...);