• דף הבית
    • אינדקס קישורים
    • פוסטים אחרונים
    • משתמשים
    • חיפוש בהגדרות המתקדמות
    • חיפוש גוגל בפורום
    • ניהול המערכת
    • ניהול המערכת - שרת private
    • הרשמה
    • התחברות
    1. דף הבית
    2. אA
    א
    מנותק
    • פרופיל
    • עוקב אחרי 0
    • עוקבים 18
    • נושאים 70
    • פוסטים 4,124
    • קבוצות 0

    אA

    @אA

    1.1k
    מוניטין
    514
    צפיות בפרופיל
    4.1k
    פוסטים
    18
    עוקבים
    0
    עוקב אחרי
    תאריך הצטרפות
    נראה לאחרונה

    אA הפסקת מעקב מעקב

    הפוסטים הטובים ביותר שנוצרו על ידי אA

    • מודול "שירות לקוחות חינמי משודרג"!!!

      אמנם נושא זה כבר נטחן בפרום, אך מכוון שכשבאתי להקים מערכת כזו, הוצרכתי לחבר כמה מודולים וכן להוסיף רעיונות, הביא אותם כאן לתועלת מי שנצרך לכך.
      כמובן שאת כל הקרדיטים אתן בסוף.

      לתגובות והארות כאן

      הבהרה

      את ההגדרות הבאתי ממערכת שפיתחתי ולא הספקתי לעבור על כל ההגדרות ולראות כל הגדרה לאיזה פרמטר היא שייכת, אני השתדל לעשות זאת בהמשך אך בינתיים השארתי את ההגדרות שלא ידעתי בהגדרה הבסיסית.
      וכן אם הגדרה מסוימת לא עובדת לכם תנסו להחליף בהגדרה אחרת מההגדרות הנוספות כי יתכן והחלפתי.
      אני אשתדל לעבור על כל ההגדרות ולבדוק אותן אחת אחת.
      אם מצאתם הגדרה לא נכונה בבקשה תדווחו לי בתגובות.
      בהצלחה!

      בהגדרות המודול ישנם הגדרות בסיסיות וישנם שניתנות להוספה לשדרוג המערכת.

      המערכת בנויה בצורה ששלוחה 1 היא העברת השיחה לנציג. כמובן שניתן לשנות.

      שלוחה 1

      ההגדרות בשלוחה:

      type=tzintuk
      tzintuk_admin=yes
      list_tzintuk=שם רשימת הצנתוקים לנציגים
      tzintuk_end=/1/1
      run_tzintuk_automat=yes
      title=מעבר לשירות הלקוחות-נציג
      

      הסבר:
      שלוחה זו היא שלוחת צינתוקים, שבכניסה לשלוחה מופעל צינתוק לנציג שמודיע לו על כך שיש שיחה בתור.
      כמובן שלפני כן הנציג נרשם לרשימה זו בשלוחה לרישום לקבלת צינתוקים.(ניתן למחוק אותה לאחר הרישום.
      לאחר שהופעל הצינתוק, המאזין עובר לשלוחה 1/1/.
      מומלץ! להגדיר שהצינתוק יהיה באורך של שנייה- שתיים, ולהגדיר בטלפון שיחה חוזרת למספר הזה, ואז הנציג לא יצטרך לעשות כלום חוץ מלהחליט אם לענות לשיחה או לנתק.

      יש להשתיק את הודעת המערכת M3338

      הגדרות נוספות:
      1.נעילת השלוחה בשעות שאינן שעות פעילות שרות הלקוחות.

      enter_if_time=07:00-06:59.*.*.*
      

      ולעלות הודעת מערכת M1114
      ניתן להוריד את הקובץ המצורף

        m1114.wav

      2.בשעות אלו, מעבר לשלוחה אחרת (92) בה ישנה אפשרות להשאיר הודעה לנציג, כדי שיחזור אליו בהמשך, או לסיים את השיחה (כפי שיובא בהמשך).

      enter_if_time_close_goto=/92
      

      שלוחה 1/1/

      ההגדרות:

      type=go_to_folder_count
      go_to_folder_count=1
      go_to_folder=/1/1/1
      go_to_folder_big=/90
      

      זוהי שלוחת מעבר לשלוחה אחרת, המוגבלת למעבר של לקוח 1, כך שאם הנציג פנוי יעבור לשלוחה 1/1/1/, ואם הוא תפוס, יעבור לשלוחה אחרת (90) להמשך המתנה, כפי שיפורט בהמשך.

      יש להשתיק את הודעת מערכת M2526

      שלוחה 1/1/1/

      ישנם 2 אפשרויות:
      1.מעבר לשיחה עם נציג.
      2.שמירת מספרו של הלקוח, כדי שהנציג יוכל לדעת את זהותו (בלקוח שאינו חדש, או בחדש שידע את המספר שלו). את האפשרות הזו נביא בהמשך.
      כמובן שבשתי האפשרויות הלקוח אינו מרגיש בשינוי ועובר להמתנה לנציג.

      מעבר לשיחה עם נציג:
      ההגדרות:

      type=confbridge
      conf_bridge_type=speech
      title=שיחה
      number_now_is=1
      

      שלוחה זו היא בעצם שלוחת חדר ועידה שהמנהל והמאזין יכולים לדבר, אך בגלל ההגבלה בשלוחה 1/ אדם נוסף אינו יכול להיכנס לשיחה.

      יש להשתיק הודעת מערכת M1008 M1007 M1816 M1815

      הגדרות נוספות:
      1.המנגינה שמשמיע בעת ההמתנה

      confbridge_music_on_hold=ztomao+patiach
      

      כאן מוגדר שמשמיע צמאה+ קריינות של "שיחתכם חשובה לנו אנא המתינו ותיענו בהקדם".
      ניתן לשנות מנגינה.
      2.שלא ישמיע את התפריט למנהל כשיכנס לשיחה (כמו שמובא בהמשך) "מספר הממתינים בחדר הועידה הוא. לכניסה לחדר הועידה הקישו אחת"

      confbridge_say_title=no
      

      וכן שלא ישמיע "ביפ" בכניסה

      confbridge_live_no_goto=wait_live
      

      זהו השלוחה של שיחה בשירות הלקוחות מוכנה!

      אבל מה יקרה כשבתוך כדי שיחה של הלקוח עם הנציג, יכנס למערכת לקוח נוסף, ויקיש 1?
      הוא יחזור חזרה לתפריט הראשי, בגלל שהגדרנו שלא יכנס יותר מלקוח 1 לשיחה!
      כך פועל שירות לקוחות!?

      לכן נפתח שלוחה נוספת בה ישהה הלקוח הנוסף עד שהנציג יתפנה!

      שלוחה 91

      ההגדרות:

      type=playfile
      playfile_error_goto=/1
      playfile_end_goto=/1
      start=min
      play_beep=no
      title=שלוחת המתנה לנציג
      number_now_is=2
      

      שלוחה זו הינה שלוחת השמעת קבצים.
      כשיכנס הלקוח לשלוחה, הוא ישמע 2 השמעות.

      1. "שיחתכם חשובה לנו אנא המתינו ותיענו בהקדם"
        2.את המנגינה שיש בשלוחה 1/1/1/ (בכדי שלא יחשוד שזו שלוחה שונה).
        ניתן להשתמש בקבצים המצורפים:
          000.wav

          001.wav

      יש לשים לב לכתוב את המספר של את הקבצים בצורה הפוכה או להגדיר שישמיע מהקובץ הקטן לגדול, בכדי שישמיע בסדר הנכון.
      לאחר שיסיים לשמוע את הקבצים הללו (מומלץ שלא יחכה יותר מידי זמן, אני ממליץ על בין דקה ל2 דקות), עובר חזרה לשלוחה 1/ שם המערכת בודקת האם הלקוח הקודם עדיין בשיחה. אם כן הוא יעבור חזרה לכאן (כמו שהגדרנו בשלוחה 1/). ישמע שוב את הקבצים והמערכת תבדוק שוב. וחוזר חלילה.

      ממולץ לתת אפשרות להשארת הודעה, כמו שיובא בהמשך.

      כעת הלקוח יכול לחכות על הקו בידיעה שכשהנציג יתפנה הוא יעבור לשיחה.

      יש להשתיק הודעת מערכת M1005
      וכעת נעבור להגדיר את השלוחה דרכה יוכל המנהל להיכנס לשיחה.

      שלוחה 4

      ההגדרות:

      type=confbridge
      conf_bridge_type=admin
      conf_bridge_folder_to_play=1/1/1
      conf_bridge_exit=yes
      conf_bridge_admin_no_ask=yes
      conf_bridge_message_start_after_beep=no
      conf_bridge_beep=no
      title=ניהול שיחה-נציג
      

      שלוחה זו היא שלוחת המעבר לשיחה עם הלקוח שנמצא בשלוחה 1/1/1/.
      כשהנציג יכנס לשלוחה, הוא יעבור ישירות לשיחה.
      שימו לב! מומלץ להכניס בשלוחה ראשית את ההגדרה

      check_did_and_go_to_folder=yes
      check_did_and_go_to_folder_one_check_only_phone=yes
      

      ולהעלות לשלוחה הראשית את הקובץ Did_Go_To.ini , ובתוכו להכניס את המספר של הנציג עם הפנייה לשלוחת הניהול,לדו',

      0770000000=/4
      

      וזה יגרום לכך שברגע שהנציג יתקשר (או שיגדיר שיחה חוזרת כמו שכתבתי למעלה), הוא יוכנס מיידית לניהול השיחה.

      הגדרות נוספות:
      1.שלא ישמיע את התפריט למנהל כשיכנס לשיחה (כמו שמובא בהמשך) "מספר הממתינים בחדר הועידה הוא. לכניסה לחדר הועידה הקישו אחת"

      conf_bridge_message_start_after_beep=no
      
      1. שלא ישמיע "ביפ" בכניסה לשיחה.
      conf_bridge_beep=no
      

      עד כאן הגדרות הבסיסיות כדי ליצור מערכת ניתוב שיחות עבור הלקוחות שלכם!
      כעת הביא עוד כמה הגדרות שניתן (ורצוי!) לשלב במערכת.

      אפשרויות בעת המתנה לנציג

      בזמן שהלקוח בשיחה עם נציג, ויש לקוח נוסף במערכת שמחכה, מומלץ לתת לו את האפשרות להשאיר הודעה לנציג כדי שיחזור אליו בהמשך.
      ולכן נפתח שלוחה נוספת.

      שלוחה 90

      ההגדרות:

      type=menu
      timeout=3
      attempts=1
      timeout_goto=/91
      title=אפשריות בעת המתנה-נציג
      

      בשלוחה זו מוגדר שיתן ללקוח תפריט לבחירה, האם להשאיר הודעה לנציג כדי שיחזור אליו כשיתפנה וכן אפשרות שישלח את ההודעה לדוא"ל שלכם או להמשיך בהמתנה.
      וכן מוגדר שהמערכת תשמיע את התפריט פעם אחת, תמתין 3 שניות, ולאחר מכן תעבור לשלוחה 91 להמשך המתנה לנציג.
      יש להעלות לשלוחה תפריט, לדו' "באפשרותכם להשאיר הודעה לשירות הלקוחות ונחזור אליכם מיד כשנתפנה. להשארת הודעה הקישו אחת. להמשך ההמתנה הקישו שתים או הַמְתִּינוּ ", אפשר גם בהקראה רובוטית.

      כעת יש לפתוח עוד 2 שלוחות בתוך שלוחה זו.

      שלוחה 90/1/

      ההגדרות:
      ישנם 2 הגדרות אפשריות:

      1. שלוחת הקלטות רגילה.
        2.שלוחת הקלטה ושליחת ההקלטה לדוא"ל המוגדר.
        ההגדרות:
      type=voicemail_email
      folder_move=/5
      voicemail_email_no_save_if_hangup=yes
      record_ok=#
      voicemail_email_add_id_to_name_record=yes
      email_address=כאן יש להגדיר כתובת מייל לשליחה
      email_name=כאן יש להגדיר מה יכתב בכותרת המייל.
      email_title=הודעות שירות הלקוחות
      voicemail_email_end_goto=hangup
      

      בשלוחה זו מוגדר הקלטת הודעה, הכנסתה לתוך שלוחה 5, ושליחת עותק שלה למייל המוגדר,עם אפשרות לתת כותרת למייל שישלח.

      שלוחה 90/2/

      ההגדרות:

      type=go_to_folder
      go_to_folder=/91
      title=מעבר להמשך המתנה
      

      שלוחה זו מוגדרת כמעבר לשלוחה אחרת (91), שהיא שלוחת ההמתנה לנציג.

      הגדרת שעות פעילות המוקד

      כמו שהבאתי בשלוחה 1/, ישנה אפשרות להגדיר שעות לפעילות המוקד, כשלאחר הזמן המוגדר יעבור הלקוח לאפשרות בחירה בין השארת הודעה לנציג, לבין סיום השיחה.

      לשם כך יש לפתוח שלוחה נוספת, ולהגדיר שבשעות שהמוקד סגור יופנה הלקוח לשלוחה זו.

      שלוחה 92

      ההגדרות:

      type=menu
      attempts=1
      timeout=5
      timeout_goto=/92/2
      title=מעבר מחוץ לשעות הפעילות
      

      בשלוחה זו מוגדר שיתן תפריט לבחירה, בין מעבר להקלטת הודעה לנציג ובין סיום השיחה.
      עוד מוגדר שהתפריט יושמע פעם אחת, כשלאחר מכן תמתין המערכת 5 שניות ואם לא תהיה בחירה על ידי הלקוח, תעבור לשלוחה 92/2/ שהיא שלוחת ניתוק ותנתק את השיחה.
      יש לעלות תפריט לדו' "להשארת הודעה הקישו 1. לסיום השיחה הקישו 2 או הַמְתִּינוּ ", אפשר גם בהקראה.

      וכעת נפתח 2 שלוחות בתוך שלוחה זו.

      שלוחה 92/1/

      ההגדרות:

      type=go_to_folder
      go_to_folder=/90/1
      title=מעבר להקלטת הודעה
      

      שלוחה זו תפנה את הלקוח להקלטת הודעה לנציג.

      שלוחה 2

      type=hangup
      title=ניתוק
      

      שלוחה זו תנתק את השיחה.

      שמיעת המספר של הלקוח (ואת שמו) לפני הכניסה לשיחה

      מה קורה כשהנציג נכנס לשיחה ולא יודע עם מי הוא הולך לדבר עם לקוח חדש, או אולי לקוח ותיק... לא נעים!
      לכן ניתן לו אפשרות לדעת מה המספר של המתקשר ובלקוחות ותיקים אפילו את שמו!

      לשם כך יש להחליף את שלוחה 1/1/1/ בשלוחה חדשה ואת השלוחה הקודמת להעביר לתוך השלוחה הזו.

      וכעת נשים בשלוחה החדשה את ההגדרות הבאות:

      שלוחה 1/1/1/

      ההגדרות:

      type=record
      hangup_insert_file_to_folder=/4/1
      folder_move=/4/1
      record_ok=#
      say_record_number=no
      say_record_menu=no
      option_record=0-1-1
      record_end_goto=/1/1/1/1
      title=שמירת מספר
      

      שלוחה זו היא בעצם שלוחת הקלטות, שבה מוגדר שבכל פעם שהמאזין יכנס לשלוחה הוא יוקלט למשך שנייה אחת, כשלאחר ההקלטה הוא יעבור לשלוחה 1/1/1/1/ שהיא ההמתנה לשיחה עם נציג
      וכן מוגדר שלא יהיה תפריט לאישור ההקלטה וכן שלא יהיה "ביפ" בתחילת ההקלטה.
      עוד מוגדר שההקלטה תעבור לשלוחה 4/1/ שהיא שלוחת הניהול

      יש להשתיק הודעות מערכת M1452 M1012 M1166

      וכעת נעבור להגדרות בצד של הנציג.

      וכעת נחליף את שלוחת הניהול (4) בהגדרות הבאות, כשאת ההגדרות הקודמות (של ניהול השיחה) נכניס לשלוחה 1 בתוך שלוחה זו (4/1/).

      שלוחה 4/

      ההגדרות:

      type=playfile
      after_play_tfr=tfr_more_options
      control_after_play_moreA1=go_to_folder
      playfile_control_play_1_goto=/4/1
      say_details_message_first=yes
      say_details_message_skip_menu=yes
      say_details_message=phone,name
      control_after_play_moreA2=go_to_folder
      playfile_control_play_2_goto=/92/2
      title=שמיעת המספר של המתקשר-נציג
      

      שלוחה זו היא בעצם שלוחת השמעת קבצים, בה יושמע הקובץ שהקלט על ידי הלקוח. וכן מוגדר שלאחר ההשמעה ישמיע את פרטי ההשמעה המוגדרים כאן שהם, הטלפון של המקליט, והשם אם מוגדר.
      ניתן להגדיר שישמיע "הודעה מאת" ואת שמו של מקליט ההודעה (שיוכנס בקובץ) כמו שכתוב כאן.
      ניתן להוריד את ההקלטה של "הודעה מאת" בהגדרה הנ"ל על ידי העלת קובץ שקט בשם M4808.

      לאחר שיסיים להשמיע את הפרטים והקובץ עובר לתפריט בחירה שמוגדר פה בצורה הזו:
      1.מעבר לשלוחה 4/1/ שהיא שלוחת הפעלת השיחה בשלוחה 1/1/1/1/.
      2. מעבר לשלוחת ניתוק השיחה של הנציג במידה ואינו יכול (או רוצה...) לענות(שלוחה 92/2/. אך שימו לב שהמתקשר עדיין ישאר בהמתנה.
      יש להשתיק הודעת מערכת M1486
      ולהעלות הודעת מערכת M1459, לדו',

        m1459.wav

      עד כאן ההגדרות שאני צירפתי. כמובן שניתן לפתח עוד ועוד, ואשמח אם תשאירו רעיונות בתגובות כדי שאוכל לעדכן את המודול.

      כמה בעיות שלא הצלחתי לפתור:

      1.לעשות תפריט בחירה ללקוח הראשון בשלוחה 1/1/1/1/ או לפחות לתת לו אפשרות לעבור לשלוחת התפריט.
      2.להשמיע את המיקום בתור.
      3.ניסיתי להחליף את שלוחת ההמתנה (91), בשלוחת מוזיקה בהמתנה שכעבור 30 שניות עובר לתפריט בחירה, אבל הוא לא עבר, אלא המשיך להשמיע את המוזיקה.
      4. יתכן שאם תגדירו שיחה חוזרת, שאם יתקשרו כמה לקוחות למערכת בבת אחת, שיחייג לכם כמה שיחות.

      וכעת לקרדיטים: תודה רבה ל כמובן ימות המשיח ולכל אלה שמשאירים פה הדרכות ברורות שבזכותם יכולתי לבנות את המודול הזה ול @חדר-1 ול @brochabar ולכל מי שנעזרתי בו ואני לא זוכר...

      לתגובות הערות והארות בשירשור
      בהצלחה!!!

      פורסם בהסברים מסודרים ממשתמשים
      א
      אA
    • מודול "השמעת הודעה כשיש הודעה חדשה"

      אמנם הנושא כבר נטחן בפורום שוב ושוב, אבל עקב כך כשהוצרכתי למודול כזה וקצת הסתבכתי, אני מעלה הדרכה מתומצת וברורה ( אני מקווה...🙄 ).
      אמנם ישנה הדרכה כזו כאן בשילוב של API, אך כיון שיש שחוששים מהעברת פרטיהם דרך שרתים אחרים, אני מעלה הדרכה שכל כולה מבוססת על המודולים של ימות המשיח.

      שירשור לתגובות

      שימו לב: מדריך זה מיועד למערכת בה יש שלוחה אחת של השמעת הודעות, ומיועד להודיע למאזינים כי יש הודעה חדשה בשלוחה.
      במערכות בהן ישנן כמה שלוחות של הודעות, ויש צורך להודיע למאזינים גם היכן יש הודעה חדשה, יש לך מדריך בהמשך הפוסט.

      שלב א':
      יש לפתוח בשלוחה הראשית שלוחה חדשה, שלוחה 900 ( בשביל הדוגמא,לא משנה איך היא תיקרא), ולהכניס בתוכה את ההגדרות הבאות:

      type=play_and_return
      play_and_return_play_all=yes
      check_access_filter=yes
      access_filter_1=h.*.*.*.*.*.*.1.*.none
      

      זוהי בעצם שלוחת השמעת הודעה אחת בלבד ללא אפשרות לדלג במהלך ההשמעה, שבה מוגדר שיתן להיכנס לכל מספר טלפון פעם אחת בלבד.
      כעת יש להעלות לשלוחה זו הקלטה שבה יושמע "יש לך הודעה חדשה בשלוחה X",
      ולשנות את שמה ל-M0002 שזוהי שמה של ההודעה המושמעת בשלוחה זו.
      הבהרה: ניתן להשתמש בשלוחה זו בהשמעת קבצים רגילה כך שגם תוכלו להשמיע הודעות טקסט, אבל לדעתי זו הכי טובה מבחינת האפשרות ללחוץ על מקש כל שהוא במהלך השהייה בה, כמו שהעירו לי.
      כמו כן ניתן (ואולי גם עדיף מה"ק) להשתמש בתפריט שידמה את התפריט המקורי(כמו שהציע @CUBASE כאן), או במעבר לשלוחה אחרת (כמו שהציע @צבי-ד-צ כאן).

      שלב ב':
      יש להכניס את ההגדרות הבאות לקובץ ההגדרות שבשלוחה הראשית,
      להגדרה שכל מי שיחייג למערכת ישמע את ההודעה

      check_did_and_go_to_folder=yes
      

      כעת יש ליצור קובץ ini בשם Did_Go_To ובו יש להכניס את המספר של המערכת ואת השלוחה להפנייה, לדוגמא

      0773137770=/900
      

      להגדרה שמספרים מסויימים ישמעו את ההודעה

      check_did_and_go_to_folder=yes
      check_did_and_go_to_folder_one_check_only_phone=yes
      

      ולהכניס בקובץ Did_Go_To את המספרים הרצויים בתוספת השלוחה להפנייה, לדוגמא

      0500000000=/900
      

      בנוסף יש להכניס את ההגדרה הבאה

      check_did_and_go_to_folder_one_time=yes
      

      שהיא בעצם מגדיר שישמיע את ההודעה פעם אחת בלבד בשיחה וכדי שיוכלו לעבור למערכת עצמה.

      שלב ג':
      בשלוחה של ההקלטות יש להגדיר שבסיום ההקלטה המקליט יועבר לשלוחה מספר 1 בתוך שלוחה זו (ניתן לשינוי), ובה תגדירו את הגדרות הבאות:

      type=access_filter
      access_filter_mode=delete
      access_filter_delete_from=השלוחה של ההודעה (בדוגמא 900).
      access_filter_delete_all=yes
      access_filter_delete_quiet=yes
      access_filter_delete_goto=/
      

      שלוחה זו בעצם מוחקת את קובץ ההגבלות בשלוחה שמשמיע את ההודעה, וכך כל מי שיתקשר ישמע שוב את ההודעה על כך "שיש הודעה בשלוחה X".

      וזהוווו!!

      קרדיט ל @ערוץ-הסקרים

      המלצה שלי (מנסיון בשימוש במודול זה): כדאי להעביר את המאזין אחרי השלוחה שמוחקת את ההגבלה לשלוחה נוספת של השמעת הודעה בודדת (זו שמובאת בתחילת הפוסט, בהגדרות השלוחה שמשמיעה את ההודעה בתפריט הראשי), ובו תכניסו את ההודעה של "ההודעה הוקלטה בהצלחה"(מצורף קובץ שיצרתי ), וזה מכיון שהרבה מאזינים מנתקים מיד כשהם שומעים את ההודעה הזו על סיום מוצלח של ההקלטה, ולא מחכים עד שהם עוברים חזרה לתפריט, וע"י זה הם מפספסים את מחיקת ההגבלה.
      כמובן שיש לזכור להשתיק את הודעת המערכת M1452 (ההודעה הוקלטה בהצלחה) בשלוחת ההקלטה.

        004_-cut_3sec.mp3

      "יש לך הודעה חדשה בשלוחה X" בשלוחות מרובות:

      ההגדרות הם אותו הדבר כמו בשלוחה אחת, רק שיש לשכפל את השלוחה כמספר השלוחות של השמעות הקבצים וכן ישנם כמה פ'יצרים להוספה:
      בסיום המדריך יש הסבר על התהליך, שיתכן ויכול לעזור להבנת המדריך.

      שלב א':
      1. יש לשכפל את השלוחה שהובאה בשלב א' למעלה כמספר שלוחות השמעת הקבצים הרצויות, ובכל שלוחה יש להוסיף הודעה שבה יושמע מספר של אחת משלוחות השמעת הקבצים, לדוג' " 1 ".
      רעיון לשידרוג: להכניס הודעות עם תוכן השלוחה לדוג', "שלוחת העדכונים", "שלוחת השמחות" וכדו'.
      2. יש להוסיף שלוחה נוספת כנ"ל ובה יש להכניס את ההודעה "יש לך הודעה חדשה בשלוחה".
      3. יש להוסיף את ההגדרה הזו שהיא בעצם ההגדרה לאן יעבור בסיום השלוחה בכל השלוחות (כולל זו של "יש לך הודעה חדשה בשלוחה").

      play_and_return_end_go_to=/1
      

      בכל שלוחה יש להגדיר מעבר לשלוחה שמשמיע את שם השלוחה הבאה בתור, לדוג', השלוחה שמשמיע את ההודעה של שלוחה 1 תעבור בסיום לשלוחה שמשמיע את ההודעה של שלוחה 2, וכן הלאה.
      רעיון לשידרוג: בשלוחות הללו (חוץ מהשלוחה שמשמיע את ההודעה "הודעה חדשה וכו') כדאי להוריד את ההשתקה של הודעת מערכת M1005 שמשתיקה את הביפ שבין ההודעות, כך שכשיהיו כמה הודעות בכמה שלוחות והמערכת תשמיע את רשימת השלוחות הנ"ל בין שלוחה לשלוחה ישמע ביפ שיפריד ביניהם.

      שלב ב':
      1. אותו הדבר כמו למעלה חוץ מזה שיש להקפיד שהמעבר יהיה לשלוחה שמשמיע את ההודעה "הודעה חדשה וכו'".

      שלב ג':
      1. יש לשכפל את השלוחה שהובאה בשלב ג' למעלה כמספר השלוחות שמשמיעות את שמות השלוחות (שלב א'), את השלוחות יש להכניס מתחת לשלוחה של ההקלטות, לדו', אם שלוחת ההקלטות היא שלוחה 1, אז יש להכניס את ההגדרות הנ"ל בשלוחה 1/1.
      כמובן שיש להגדיר בשלוחת ההקלטות שבסיום ההקלטה יעבור המאזין דרך שלוחה זו.
      2. במספר השלוחה יש להגדיר את השלוחה שמשמיע את שם השלוחה בתפריט הראשי, לדו' בשלוחה שתחת שלוחת ההקלטות שמקליטה לשלוחה 1 שלוחה 1, יש להגדיר את מספר השלוחה שמשמיע בתפריט הראשי את השם של שלוחה 1, וכן הלאה.
      3. בנוסף מתחת לכל שלוחה כזו יש להוסיף שלוחה נוספת כנ"ל, ובה יש להגדיר את מספר השלוחה שמשמיע את ההודעה "הודעה חדשה בשלוחה".

      הסבר: התהליך שיבוצע הוא פשוט, בסיום כל הקלטה המקליט ימחק (באופן אוטומטי ושקט כמובן) את קובץ ההגבלה של השלוחות שמשמיעות את ההודעות בשלוחה הראשית, לדו', כשאני מקליט הודעה לשלוחה 1, בסיום ההקלטה אני אמחק את ההגבלה על השלוחה שמשמיע את ההודעה "יש לך הודעה חדשה בשלוחה" וכן את ההגבלה על השלוחה שמשמיעה את שם השלוחה "1", כך שמה שיושמע לנכנסים לקו הוא "הודעה חדשה בשלוחה 1".
      שימו לב: יש לזכור להגדיר בסופן של כל השלוחות שיעבור לשלוחה שתחתיהן, לדו', שלוחה 2 (הקלטות לשלוחה 1) תעביר בסוף ההקלטה לשלוחה 2/1 שהיא מוחקת את ההקלטה של שם השלוחה שלה, והיא בסיום תעביר לשלוחה 2/1/1 שהיא תמחוק את ההקלטה של "הודעה חדשה וכו'.

      המלצה: המדריך אולי נראה מסובך, אבל עם תעשו כמו שכתוב, זה יעשה פשוט מאוד.

      המלצה שלי (מנסיון בשימוש במודול זה): כדאי להעביר את המאזין אחרי השלוחה שמוחקת את ההגבלה לשלוחה נוספת של השמעת הודעה בודדת (זו שמובאת בתחילת הפוסט, בהגדרות השלוחה שמשמיעה את ההודעה בתפריט הראשי), ובו תכניסו את ההודעה של "ההודעה הוקלטה בהצלחה"(מצורף קובץ שיצרתי),וזה מכיון שהרבה מאזינים מנתקים מיד כשהם שומעים את ההודעה הזו על סיום מוצלח של ההקלטה, ולא מחכים עד שהם עוברים חזרה לתפריט, וע"י זה הם מפספסים את מחיקת ההגבלה.
      כמובן שיש לזכור להשתיק את הודעת המערכת M1452 (ההודעה הוקלטה בהצלחה) בשלוחת ההקלטה.

        004_-cut_3sec.mp3

      בהצלחה!!!

      שירשור לתגובות.

      פורסם בהסברים מסודרים ממשתמשים
      א
      אA
    • קבצים ותוכנות לשימוש במערכות ימות המשיח

      לתגובות

      לאור כך שבתקופה האחרונה הרבה משתמשים יקרים כאן בפורום העלו לטובת כלל המשתמשים קבצי HTML שמקילים מאוד את השימוש במערכות ללא צורך להיכנס למערכת בכל פעם, חשבתי שכדאי לקבץ את כל הקבצים לשירשור אחד, כך יוכלו למצוא אותם בקלות.
      אשמח שכל מי שנתקל בקבצים שעדיין לא הועלו כאן שיעדכן בשירשור עם הקרדיט למפתח.

      שימו לב! עקב בעיה בהעלאת הקבצים לפורום ניתן להורידם כאן.
      https://mitmachim.top/assets/uploads/files/1766433643666-0ef717c9-96cf-4412-9005-b17ee5626ab9-downloads.rar

      בנוסף אני אעדכן את הקודים של הקבצים.
      כדי לפותחם כקובץ יש להעתיק את הטקסט לקובץ טקסט פשוט >קובץ>שמור בשם,ולשמור בשם כל שהוא, ובסוף נקודה (.) ואז html. בנוסף בסוג קובץ לבחור All Files ואז שמור, וזהו.

      סרטון לדוגמא
      סרטון לדוגמא

      עידכון: אני מעלה את הקבצים גם כתוכנות ניידות.
      ניתן להוריד כאן

      ניתן להוריד את התוכנה הזו ולהכניס לתוכה את קובץ הhtml והיא תיצור ממנו תוכנה ניידת.
      בונה_התוכנות.zip

      רשימת הקבצים: (מתעדכן)

      קובץ להוספת והגדרת שלוחות במערכות (מתעדכן)
      קובץ להעלאת קבצי שמע למערכת
      קובץ להוספת נקודות למאזינים
      תוכנה להורדה של רשימת הצינתוקים למחשב
      קובץ לצפייה בדוחות של קבלת נתונים
      קובץ ליצירת שלוחות API
      קובץ להורדת קבצים מהמערכת (קבצי wav, tts, ini, html) כולל אפשרות להמרה + אפשרות להורדת קבצים מרובים

      קובץ להעלאה והורדה של כל סוגי הקבצים למערכות כולל קבצי ini - עם אפשרות להפעלת צינתוק בסיום העלאה!

      קובץ להפעלת קמפיין - הנכנס לשלוחה במערכת
      ממשק להתקנת תא קולי מתקדם על המערכת שלכם בהכנסת מספר וסיסמא בלבד!
      קובץ להעברת שלוחות וקבצים בין מערכות (גם שלוחות שלמות!)
      קובץ להעלאת תקיות וקבצים למערכת - בצורת עץ שלוחות
      קובץ לשינוי פרטי הקובץ בהשמעת קבצים
      קובץ לפתיחת שלוחת טרוויה
      קובץ לניתוח נתוני המערכת

      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • השנה ההתרמה שלכם תראה אחרת!!!

      לקראת ימי הפורים המתקרבים, שמח להציג ממשק מיוחד שיוכל לעזור לכם מאוד בהתרמה וניהול החפ"ק שלכם.

      שימו לב! זוהי גירסא ראשונית. בעז"ה אני מקווה לשפר אותה יחד עם הארות והערות של המשתמשים.
      כמו כן יש לבצע הרצה לממשק לפי שימוש נרחב.

      הממשק מורכב כרגע משתי חלקים:
      1. הממשק הויזואלי.
      2.הממשק הטלפוני.

      1. הממשק הויזואלי:
      הממשק כולל תוכנה אותה ניתן להקרין בחפ"ק ובה מתעדכנים הנתונים אונליין הן מהמערכת הטלפוני והן ממשק הניהול.
      בממשק מופיעים התרומות האחרונות כולל שם המתרים ושעת ההתרמה, וכן 10 המתרימים המובילים, וכמובן ספירה לאחור עד לסיום הקמפיין ועידכון אונליין של הסכום שנאסף עד כה ואחוזי האסיפה.

      בנוסף יש את ממשק הניהול שהיא תוכנה נפרדת דרכה ניתן לעדכן בכל עת תרומות חדשות שהתקבלו והם יסונכרנו הן במסך והן במערכת הטלפונית.
      כמו כן ניתן לנהל את הגדרות ההתרמה, תאריך ושעת הסיום, יעד ההתרמה, וכן את הפעולות האחרונות שהוכנסו והסכומים של כלל המתרימים.

      2.המערכת הטלפונית:
      במערכת ניתן לעדכן את הסכום אותו אסף כל מתרים באמצעות מספר אישי (ת.ז. או מספר מיוחד), וכן לשמוע את הסכום הכולל שנאסף עד כה.
      שימו לב! הסכומים מסתנכרנים יחד עם הממשק גם בטלפון!

      מה נצרך כדי לתפעל את ההתרמה?
      1. מחשב עם חיבור אינטרנטי רציף.
      2. חיבור אינטרנטי יציב ומהיר.
      וזהו!

      שימו לב!
      ניתן לעבוד על הקמפיין מכמה מחשבים בו זמנית וזה מכיוון שכל הנתונים גם הידנים נכנסים לקובץ במערכת הטלפונית ומשם הנתונים נלקחים!!!

      שימו לב שהטוקן אחיד כדי שכולם יוכלו לעבוד יחד!

      וכעת להגדרות למערכת הטלפונית:

      ; הגדרת השלוחה כהוספת נקודות
      type=points_save
      ; שיתן לבחור את סכום הנקודות
      points_tfr_add=yes
      ; הזיהוי של תעודת זהות
      enter_id_type=teudat_zehut
      enter_id=yes
      ; שישמיע את השם בכניסה
      login_add_val_name=yes
      ; שלא יבקש להקליט את השם
      record_name=no
      ; שבכל כניסה לשלוחה יבקש זיהוי מחדש
      delete_id_exit=yes
      ; שיקח את השמות מתקיית הניקוד
      login_add_val_name_folder=/Points
      

      בנוסף בתקייה Points בשלוחה הראשית יש לפתוח 3 קבצי ini.
      1. manual_log.ini
      2. config.ini
      3. EnterIDValName

      כמובן שיש להחליף את ההקלטות הנצרכות בעז"ה הביא אותם בהמשך.

      וכעת לממשקים:
      1. מסך ההתרמה

      כתוכנה
      https://mitmachim.top/topic/93250/מדריך-השנה-ההתרמה-שלכם-תראה-אחרת

      הקוד


      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
      <meta charset="UTF-8">
      <title>מערכת התרמה LIVE - Points Sync</title>
      <style>
      :root { --accent: #0ea5e9; --gold: #fbbf24; --bg: #020617; }
      body { background: var(--bg); color: #f8fafc; font-family: system-ui, sans-serif; margin: 0; padding: 10px; overflow: hidden; }
      .container { display: grid; grid-template-columns: 320px 1fr 320px; gap: 15px; height: 95vh; }
      .card { background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 20px; padding: 15px; backdrop-filter: blur(10px); display: flex; flex-direction: column; overflow: hidden; }
      .center-card { display: flex; flex-direction: column; justify-content: space-between; align-items: center; text-align: center; padding: 40px 20px; }
      h2 { font-size: 1.3rem; border-bottom: 2px solid var(--accent); padding-bottom: 8px; margin: 0 0 10px 0; color: white; text-align: center; }

          .countdown-timer { display: flex; gap: 15px; margin-bottom: 20px; }
          .time-unit { background: rgba(14, 165, 233, 0.2); padding: 10px 15px; border-radius: 12px; border: 1px solid var(--accent); min-width: 60px; }
          .time-num { display: block; font-size: 2rem; font-weight: bold; color: white; }
          .time-label { font-size: 0.8rem; opacity: 0.7; }
      
          .total-amount { font-size: 8rem; font-weight: 900; color: var(--accent); text-shadow: 0 0 30px rgba(14, 165, 233, 0.4); margin: 10px 0; line-height: 1; }
          
          .progress-wrapper { width: 90%; }
          .progress-bg { background: #1e293b; height: 40px; border-radius: 50px; border: 2px solid #334155; overflow: hidden; position: relative; }
          .progress-fill { height: 100%; background: linear-gradient(90deg, #0ea5e9, #22d3ee); width: 0%; transition: width 2s; }
          
          .list-box { flex-grow: 1; overflow: hidden; display: flex; flex-direction: column; }
          .item { display: flex; justify-content: space-between; align-items: center; padding: 10px; background: rgba(255,255,255,0.05); margin-bottom: 8px; border-radius: 10px; border-right: 4px solid var(--accent); flex-shrink: 0; }
          .item-info { display: flex; flex-direction: column; }
          .item-name { font-weight: bold; font-size: 1.1rem; display: flex; align-items: center; gap: 6px; }
          .item-time { font-size: 0.8rem; color: #94a3b8; font-weight: bold; }
          .item-val { font-weight: 900; color: var(--gold); font-size: 1.3rem; }
          
          .badge { font-size: 0.6rem; padding: 2px 5px; border-radius: 4px; font-weight: bold; }
          .badge-manual { background: #0ea5e9; color: white; }
          .badge-phone { background: #22c55e; color: white; }
      
          .live-dot { height: 10px; width: 10px; background: #ef4444; border-radius: 50%; display: inline-block; margin-left: 8px; animation: pulse 1s infinite; }
          @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
      </style>
      

      </head>
      <body>

      <div class="container">
      <div class="card">
      <h2>🏆 מובילים</h2>
      <div id="leaderboard" class="list-box"></div>
      </div>

      <div class="card center-card">
          <div>
              <div style="margin-bottom: 10px; font-size: 1.2rem; font-weight: bold;"><span class="live-dot"></span>הקמפיין מסתיים בעוד:</div>
              <div id="countdown" class="countdown-timer">
                  <div class="time-unit"><span id="days" class="time-num">00</span><span class="time-label">ימים</span></div>
                  <div class="time-unit"><span id="hours" class="time-num">00</span><span class="time-label">שעות</span></div>
                  <div class="time-unit"><span id="minutes" class="time-num">00</span><span class="time-label">דקות</span></div>
                  <div class="time-unit"><span id="seconds" class="time-num">00</span><span class="time-label">שניות</span></div>
              </div>
          </div>
      
          <div>
              <h1 style="margin:0; font-size: 1.8rem; opacity: 0.9;">סך הכל נאסף:</h1>
              <div id="main-total" class="total-amount">₪0</div>
          </div>
      
          <div class="progress-wrapper">
              <div class="progress-bg"><div id="progress-bar" class="progress-fill"></div></div>
              <div id="percent-label" style="font-size: 3rem; color: var(--gold); font-weight: bold; margin-top: 10px;">0%</div>
              <div id="goal-text" style="font-size: 1.5rem; opacity: 0.6;">מחשב נתונים...</div>
          </div>
      </div>
      
      <div class="card">
          <h2>🔔 תרומות אחרונות</h2>
          <div id="recent-log" class="list-box"></div>
      </div>
      

      </div>

      <script>
      // הגדרות שלוחה - הכל תחת תיקיית Points
      const TOKEN = localStorage.getItem('campaign_token') || 'Token';
      const PATH_TOTAL = 'ivr2:Points/points_total.ymgr';
      const PATH_PHONE_LOG = 'ivr2:Points/points_log.2026-02.ymgr';
      const PATH_MANUAL_LOG = 'ivr2:Points/manual_log.ini';
      const PATH_CONFIG = 'ivr2:Points/config.ini';
      const PATH_NAMES = 'ivr2:Points/EnterIDValName.ini';

      let GOAL = 500000;
      let DEADLINE = new Date(); 
      let nameMap = {};
      
      function updateCountdown() {
          const now = new Date();
          const diff = DEADLINE - now;
          if (diff <= 0) {
              document.querySelectorAll('.time-num').forEach(el => el.innerText = "00");
              return;
          }
          document.getElementById('days').innerText = String(Math.floor(diff / (1000 * 60 * 60 * 24))).padStart(2, '0');
          document.getElementById('hours').innerText = String(Math.floor((diff / (1000 * 60 * 60)) % 24)).padStart(2, '0');
          document.getElementById('minutes').innerText = String(Math.floor((diff / 1000 / 60) % 60)).padStart(2, '0');
          document.getElementById('seconds').innerText = String(Math.floor((diff / 1000) % 60)).padStart(2, '0');
      }
      
      async function update() {
          try {
              const [resTotal, resLogPhone, resNames, resConfig, resLogManual] = await Promise.all([
                  fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_TOTAL}`).then(r => r.json()),
                  fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_PHONE_LOG}`).then(r => r.json()),
                  fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_NAMES}`).then(r => r.json()),
                  fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_CONFIG}`).then(r => r.json()),
                  fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_MANUAL_LOG}`).then(r => r.json())
              ]);
      
              // עדכון יעד ותאריך
              if (resConfig.contents) {
                  const g = resConfig.contents.match(/goal=(.*)/);
                  const d = resConfig.contents.match(/date=(.*)/);
                  if (g) {
                      GOAL = parseFloat(g[1]);
                      document.getElementById('goal-text').innerText = `מתוך יעד של ${GOAL.toLocaleString()} ₪`;
                  }
                  if (d) DEADLINE = new Date(d[1]);
              }
      
              // מיפוי שמות
              if (resNames.contents) {
                  resNames.contents.split(/[\r\n]+/).forEach(line => {
                      const parts = line.split('=');
                      if (parts.length === 2) nameMap[parts[0].trim()] = parts[1].trim();
                  });
              }
      
              // חישוב טוטאל ומובילים
              if (resTotal.contents) {
                  const lines = resTotal.contents.trim().split(/[\r\n]+/);
                  let totalSum = 0; let users = [];
                  lines.forEach(l => {
                      const idMatch = l.match(/EnterId#(\d+)/);
                      const scoreMatch = l.match(/PointsTotalAll#([\d.]+)/);
                      if (idMatch && scoreMatch) {
                          const val = parseFloat(scoreMatch[1]);
                          totalSum += val;
                          users.push({id: idMatch[1], val: val});
                      }
                  });
                  document.getElementById('main-total').innerText = '₪' + totalSum.toLocaleString();
                  document.getElementById('progress-bar').style.width = Math.min((totalSum/GOAL)*100, 100) + '%';
                  document.getElementById('percent-label').innerText = ((totalSum/GOAL)*100).toFixed(1) + '%';
                  
                  users.sort((a,b) => b.val - a.val);
                  document.getElementById('leaderboard').innerHTML = users.map((u,i)=>`
                      <div class="item">
                          <span class="item-name"><b>${i+1}.</b> ${nameMap[u.id] || u.id}</span>
                          <span style="color:var(--accent); font-weight:bold;">₪${u.val.toLocaleString()}</span>
                      </div>
                  `).join('');
              }
      
              // לוג תרומות אחרונות
              let allItems = [];
              const todayStr = new Date().toISOString().split('T')[0];
      
              if (resLogPhone.contents) {
                  resLogPhone.contents.trim().split(/[\r\n]+/).forEach((line, idx) => {
                      const idM = line.match(/id#(\d+)/);
                      const ptM = line.match(/Points#(\d+)/);
                      const timeM = line.match(/(\d{2}:\d{2})/);
                      if (idM && ptM) {
                          let ts = timeM ? new Date(`${todayStr}T${timeM[1]}:00`).getTime() : (Date.now() - 500000);
                          allItems.push({ id: idM[1], amount: ptM[1], type: 'phone', timestamp: ts, timeStr: timeM ? timeM[1] : "--:--" });
                      }
                  });
              }
      
              if (resLogManual.contents) {
                  resLogManual.contents.trim().split('\n').forEach(l => {
                      let [ts, id, amt, time] = l.split('|');
                      if (id) allItems.push({ id, amount: amt, type: 'manual', timestamp: parseInt(ts), timeStr: time });
                  });
              }
      
              allItems.sort((a, b) => b.timestamp - a.timestamp);
      
              // כאן הורדנו את ה-slice כדי שהריבוע יתמלא לגמרי
              document.getElementById('recent-log').innerHTML = allItems.map(item => `
                  <div class="item">
                      <div class="item-info">
                          <span class="item-name">
                              <span class="badge ${item.type === 'manual' ? 'badge-manual' : 'badge-phone'}">${item.type === 'manual' ? 'ידני' : 'טלפון'}</span>
                              ${nameMap[item.id] || 'מתרים ' + item.id}
                          </span>
                          <span class="item-time">${item.timeStr}</span>
                      </div>
                      <span class="item-val">₪${parseFloat(item.amount).toLocaleString()}</span>
                  </div>
              `).join('');
      
          } catch (e) { console.error("Update Error:", e); }
      }
      
      setInterval(updateCountdown, 1000);
      setInterval(update, 5000);
      updateCountdown();
      update();
      

      </script>
      </body>
      </html>

      2. ניהול ההתרמה

      כתוכנה
      https://mitmachim.top/topic/93250/מדריך-השנה-ההתרמה-שלכם-תראה-אחרת

      הקוד


      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
      <meta charset="UTF-8">
      <title>מנהל קמפיין - Points Sync</title>
      <style>
      :root { --primary: #0ea5e9; --success: #22c55e; --bg: #0f172a; --card: #1e293b; --border: #334155; --gold: #f59e0b; }
      body { background: var(--bg); color: #f1f5f9; font-family: system-ui, sans-serif; margin: 0; padding: 20px; text-align: right; }
      .manual-entry-box { max-width: 600px; margin: 50px auto; background: var(--card); padding: 30px; border-radius: 20px; box-shadow: 0 10px 25px rgba(0,0,0,0.3); border: 1px solid var(--border); }
      h1 { text-align: center; color: var(--primary); }
      label { display: block; margin-top: 15px; font-weight: bold; }
      input { width: 100%; padding: 15px; margin: 10px 0; border-radius: 10px; border: 1px solid var(--border); background: #0f172a; color: white; font-size: 1.2rem; box-sizing: border-box; }
      .main-btn { background: var(--success); color: white; border: none; padding: 15px; border-radius: 10px; cursor: pointer; font-weight: bold; width: 100%; font-size: 1.1rem; margin-top: 20px; transition: 0.3s; }
      .main-btn:hover { filter: brightness(1.1); transform: translateY(-2px); }
      .settings-trigger { position: fixed; top: 20px; left: 20px; background: #334155; color: white; border: none; padding: 12px 20px; border-radius: 50px; cursor: pointer; font-weight: bold; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
      .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); z-index: 1000; backdrop-filter: blur(5px); }
      .modal-content { background: var(--card); width: 90%; max-width: 800px; margin: 40px auto; border-radius: 20px; padding: 25px; border: 1px solid var(--border); position: relative; max-height: 85vh; overflow-y: auto; }
      .close-btn { position: absolute; top: 15px; right: 20px; font-size: 1.5rem; cursor: pointer; }
      .tabs { display: flex; gap: 5px; border-bottom: 2px solid var(--border); margin-bottom: 20px; }
      .tab-btn { background: none; border: none; color: #8b949e; padding: 10px 15px; cursor: pointer; font-size: 0.95rem; }
      .tab-btn.active { color: var(--primary); border-bottom: 2px solid var(--primary); font-weight: bold; }
      .tab-pane { display: none; }
      .tab-pane.active { display: block; }
      .log-list { background: #0f172a; padding: 10px; border-radius: 8px; max-height: 350px; overflow-y: auto; }
      .log-item { display: flex; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border); align-items: center; }
      .log-item label { display: flex; align-items: center; cursor: pointer; width: 100%; gap: 15px; }
      .badge { font-size: 0.75rem; padding: 3px 8px; border-radius: 5px; color: white; font-weight: bold; }
      .badge-manual { background: var(--primary); }
      .badge-phone { background: var(--success); }
      .status { margin-top: 15px; font-size: 0.9rem; text-align: center; font-weight: bold; }
      </style>
      </head>
      <body>

      <button class="settings-trigger" onclick="openModal()"><span>⚙️</span> הגדרות קמפיין</button>

      <div class="manual-entry-box">
      <h1>💰 הזנת תרומה ידנית</h1>
      <label>מספר אישי של המתרים:</label>
      <input type="number" id="manualId" placeholder="למשל 215" autofocus>
      <label>סכום התרומה (₪):</label>
      <input type="number" id="manualAmount" placeholder="0.00">
      <button class="main-btn" onclick="executeManualDonation()">✅ אשר והוסף למערכת</button>
      <div id="mainStatus" class="status"></div>
      </div>

      <div id="settingsModal" class="modal">
      <div class="modal-content">
      <span class="close-btn" onclick="closeModal()">✖️</span>
      <h2 style="border-bottom: 1px solid var(--border); padding-bottom: 10px;">⚙️ ניהול והגדרות</h2>
      <div class="tabs">
      <button class="tab-btn active" onclick="switchTab(event, 'tabNames')">👥 שמות</button>
      <button class="tab-btn" onclick="switchTab(event, 'tabGoal')">📈 יעד ותאריך</button>
      <button class="tab-btn" onclick="switchTab(event, 'tabTotals')">📊 עריכת סכומים</button>
      <button class="tab-btn" onclick="switchTab(event, 'tabLogs')">🗑️ מחיקת לוג</button>
      </div>

          <div id="tabNames" class="tab-pane active">
              <div style="display:flex; gap:10px;"><input type="text" id="editId" placeholder="מספר אישי"><button onclick="findName()" style="width:100px; background:var(--primary); border:none; border-radius:8px; color:white; cursor:pointer;">חפש</button></div>
              <input type="text" id="editName" placeholder="שם המתרים">
              <button class="main-btn" style="background:var(--primary)" onclick="saveName()">שמור שם</button>
          </div>
      
          <div id="tabGoal" class="tab-pane">
              <label>יעד הקמפיין (₪):</label><input type="number" id="goalVal">
              <label>תאריך ושעת סיום:</label><input type="datetime-local" id="dateVal">
              <button class="main-btn" onclick="saveConfig()">🔄 עדכן יעד ותאריך</button>
              <hr style="border:0; border-top:1px solid var(--border); margin:25px 0;">
              <label style="color:var(--gold)">מפתח אבטחה (Token) API:</label>
              <input type="password" id="tokenInput" placeholder="הכנס טוקן חדש">
              <button class="main-btn" style="background:#6366f1" onclick="updateToken()">🔄 עדכן מפתח Token</button>
          </div>
      
          <div id="tabTotals" class="tab-pane"><div id="totalsList" class="log-list">טוען נתונים...</div></div>
          <div id="tabLogs" class="tab-pane">
              <div id="logsList" class="log-list">טוען לוג פעולות מהשרת...</div>
              <button class="main-btn" style="background:#ef4444" onclick="deleteLogs()">❌ מחק פעולות וסנכרן שרת</button>
          </div>
          <div id="modalStatus" class="status"></div>
      </div>
      

      </div>

      <script>
      let CURRENT_TOKEN = localStorage.getItem('campaign_token') || 'YOUR_TOKEN_HERE';
      let nameMap = {};

      // נתיבים בשרת - הכל בתיקיית Points
      const PATH_TOTAL = 'ivr2:Points/points_total.ymgr';
      const PATH_PHONE_LOG = 'ivr2:Points/points_log.2026-02.ymgr';
      const PATH_MANUAL_LOG = 'ivr2:Points/manual_log.ini';
      const PATH_CONFIG = 'ivr2:Points/config.ini';
      const PATH_NAMES = 'ivr2:Points/EnterIDValName.ini';
      
      document.getElementById('tokenInput').value = CURRENT_TOKEN;
      
      function updateToken() {
          const newToken = document.getElementById('tokenInput').value.trim();
          if(!newToken) return alert("נא להזין טוקן");
          CURRENT_TOKEN = newToken;
          localStorage.setItem('campaign_token', newToken);
          alert("הטוקן עודכן במחשב זה!");
          location.reload();
      }
      
      function openModal() { document.getElementById('settingsModal').style.display = 'block'; }
      function closeModal() { document.getElementById('settingsModal').style.display = 'none'; }
      
      async function callApi(cmd, path, content = "") {
          let url = `https://www.call2all.co.il/ym/api/${cmd}?token=${CURRENT_TOKEN}&what=${path}`;
          if(content) url += `&contents=${encodeURIComponent(content)}`;
          return fetch(url).then(r => r.json());
      }
      
      async function switchTab(e, id) {
          document.querySelectorAll('.tab-pane, .tab-btn').forEach(el => el.classList.remove('active'));
          document.getElementById(id).classList.add('active');
          e.currentTarget.classList.add('active');
          await fetchNames();
          if(id === 'tabGoal') loadCurrentConfig();
          if(id === 'tabTotals') fetchTotals();
          if(id === 'tabLogs') fetchLogs();
      }
      
      async function fetchNames() {
          const res = await callApi('GetTextFile', PATH_NAMES);
          if (res.contents) {
              nameMap = {};
              res.contents.split(/[\r\n]+/).forEach(line => {
                  const parts = line.split('=');
                  if (parts.length === 2) nameMap[parts[0].trim()] = parts[1].trim();
              });
          }
      }
      
      async function executeManualDonation() {
          const id = document.getElementById('manualId').value;
          const amt = parseFloat(document.getElementById('manualAmount').value);
          if(!id || !amt) return alert("מלא מספר וסכום");
          document.getElementById('mainStatus').innerText = "⏳ מעדכן שרת...";
      
          let resT = await callApi('GetTextFile', PATH_TOTAL);
          let tLines = (resT.contents || "").trim().split('\n');
          let found = false;
          let updatedT = tLines.map(l => {
              if(l.includes(`EnterId#${id}`)) {
                  found = true;
                  let old = parseFloat((l.match(/PointsTotalAll#([\d.]+)/) || [0,0])[1]);
                  let sum = old + amt;
                  return l.replace(/PointsTotalAll#[\d.]+/, `PointsTotalAll#${sum}`).replace(/PointsTotal#[\d.]+/, `PointsTotal#${sum}`);
              }
              return l;
          });
          if(!found) updatedT.push(`EnterId#${id}%PointsTotalAll#${amt}%PointsTotal#${amt}`);
          
          let resL = await callApi('GetTextFile', PATH_MANUAL_LOG);
          let currentManualLog = resL.contents || "";
          const timeStr = new Date().toLocaleTimeString('he-IL', {hour:'2-digit', minute:'2-digit'});
          const newEntry = `${Date.now()}|${id}|${amt}|${timeStr}`;
          const updatedManualLog = newEntry + "\n" + currentManualLog;
      
          await Promise.all([
              callApi('UploadTextFile', PATH_TOTAL, updatedT.join('\n')),
              callApi('UploadTextFile', PATH_MANUAL_LOG, updatedManualLog)
          ]);
      
          document.getElementById('mainStatus').innerText = "✅ נרשם וסונכרן לשרת!";
          document.getElementById('manualAmount').value = "";
      }
      
      async function fetchLogs() {
          let [resPhone, resManual] = await Promise.all([
              callApi('GetTextFile', PATH_PHONE_LOG),
              callApi('GetTextFile', PATH_MANUAL_LOG)
          ]);
      
          let allEntries = [];
          const todayStr = new Date().toISOString().split('T')[0];
      
          if (resPhone.contents) {
              resPhone.contents.trim().split('\n').forEach(l => {
                  let id = (l.match(/id#(\d+)/) || [])[1];
                  let pts = (l.match(/Points#(\d+)/) || [])[1];
                  let timeM = (l.match(/(\d{2}:\d{2})/) || [""])[0];
                  if (id) allEntries.push({id, pts, time: timeM, type: 'phone', fullLine: l, ts: timeM ? new Date(`${todayStr}T${timeM}:00`).getTime() : 0});
              });
          }
      
          if (resManual.contents) {
              resManual.contents.trim().split('\n').forEach(l => {
                  let [ts, id, amt, time] = l.split('|');
                  if (id) allEntries.push({id, pts: amt, time, type: 'manual', fullLine: l, ts: parseInt(ts)});
              });
          }
      
          allEntries.sort((a, b) => b.ts - a.ts);
      
          let html = allEntries.map(entry => {
              let name = nameMap[entry.id] || entry.id;
              return `<div class="log-item"><label><input type="checkbox" class="log-cb" data-id="${entry.id}" data-amt="${entry.pts}" data-type="${entry.type}" data-line="${entry.fullLine}"><span><span class="badge ${entry.type === 'manual' ? 'badge-manual' : 'badge-phone'}">${entry.type === 'manual' ? 'ידני' : 'טלפון'}</span> [${entry.time}] <b>${name}</b> - ₪${entry.pts}</span></label></div>`;
          }).join('');
          document.getElementById('logsList').innerHTML = html || "אין היסטוריה.";
      }
      
      async function deleteLogs() {
          const checkboxes = document.querySelectorAll('.log-cb:checked');
          if (checkboxes.length === 0) return alert("בחר פעולות");
          if (!confirm("זה ימחק את השורות מכל המחשבים ויקזז סכומים. המשך?")) return;
      
          document.getElementById('modalStatus').innerText = "⏳ מסנכרן שרת...";
          let [resP, resM, resT] = await Promise.all([
              callApi('GetTextFile', PATH_PHONE_LOG),
              callApi('GetTextFile', PATH_MANUAL_LOG),
              callApi('GetTextFile', PATH_TOTAL)
          ]);
      
          let phoneLines = resP.contents ? resP.contents.trim().split('\n') : [];
          let manualLines = resM.contents ? resM.contents.trim().split('\n') : [];
          let totalLines = resT.contents ? resT.contents.trim().split('\n') : [];
      
          checkboxes.forEach(cb => {
              const id = cb.getAttribute('data-id'), amt = parseFloat(cb.getAttribute('data-amt')), type = cb.getAttribute('data-type'), line = cb.getAttribute('data-line');
              if (type === 'phone') phoneLines = phoneLines.filter(l => l !== line);
              else manualLines = manualLines.filter(l => l !== line);
      
              totalLines = totalLines.map(tL => {
                  if(tL.includes(`EnterId#${id}`)) {
                      let old = parseFloat((tL.match(/PointsTotalAll#([\d.]+)/) || [0,0])[1]);
                      let sum = Math.max(0, old - amt);
                      return tL.replace(/PointsTotalAll#[\d.]+/, `PointsTotalAll#${sum}`).replace(/PointsTotal#[\d.]+/, `PointsTotal#${sum}`);
                  } return tL;
              });
          });
      
          await Promise.all([
              callApi('UploadTextFile', PATH_PHONE_LOG, phoneLines.join('\n')),
              callApi('UploadTextFile', PATH_MANUAL_LOG, manualLines.join('\n')),
              callApi('UploadTextFile', PATH_TOTAL, totalLines.join('\n'))
          ]);
          
          document.getElementById('modalStatus').innerText = "✅ הסנכרון הושלם בכל המחשבים!";
          fetchLogs();
      }
      
      async function loadCurrentConfig() {
          const res = await callApi('GetTextFile', PATH_CONFIG);
          if(res.contents) {
              const goal = (res.contents.match(/goal=(.*)/) || [])[1];
              const date = (res.contents.match(/date=(.*)/) || [])[1];
              if(goal) document.getElementById('goalVal').value = goal;
              if(date) document.getElementById('dateVal').value = date;
          }
      }
      async function saveConfig() {
          const content = `goal=${document.getElementById('goalVal').value}\ndate=${document.getElementById('dateVal').value}`;
          await callApi('UploadTextFile', PATH_CONFIG, content);
          alert("עודכן בשרת!");
      }
      async function fetchTotals() {
          let res = await callApi('GetTextFile', PATH_TOTAL);
          if(!res.contents) return;
          let html = res.contents.trim().split('\n').map(l => {
              let id = (l.match(/EnterId#(\d+)/) || [])[1];
              let val = (l.match(/PointsTotalAll#([\d.]+)/) || [])[1];
              if(!id) return '';
              let name = nameMap[id] || id;
              return `<div class="log-item"><span><b>${name}</b> (${id})</span><div style="display:flex; gap:5px;"><input type="number" id="inp-${id}" value="${val}" style="width:110px;"><button onclick="updateTotal('${id}')" style="background:var(--primary); color:white; border:none; padding:8px 12px; border-radius:8px; cursor:pointer;">עדכן</button></div></div>`;
          }).join('');
          document.getElementById('totalsList').innerHTML = html;
      }
      async function updateTotal(id) {
          const newVal = document.getElementById(`inp-${id}`).value;
          let res = await callApi('GetTextFile', PATH_TOTAL);
          let updated = res.contents.trim().split('\n').map(l => l.includes(`EnterId#${id}`) ? l.replace(/PointsTotalAll#[\d.]+/, `PointsTotalAll#${newVal}`).replace(/PointsTotal#[\d.]+/, `PointsTotal#${newVal}`) : l);
          await callApi('UploadTextFile', PATH_TOTAL, updated.join('\n'));
          alert("סכום עודכן בשרת!");
      }
      async function findName() {
          const id = document.getElementById('editId').value;
          await fetchNames();
          document.getElementById('editName').value = nameMap[id] || "";
      }
      async function saveName() {
          const id = document.getElementById('editId').value;
          const name = document.getElementById('editName').value;
          let res = await callApi('GetTextFile', PATH_NAMES);
          let lines = (res.contents || "").split('\n').filter(l => l.trim() && !l.startsWith(`${id}=`));
          lines.push(`${id}=${name}`);
          await callApi('UploadTextFile', PATH_NAMES, lines.join('\n'));
          alert("שם עודכן בשרת!");
          await fetchNames();
      }
      

      </script>
      </body>
      </html>

      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • המודולים של CUBASE | מתעדכן

      אחד המשתמשים האלופים כאן בפורום זה @CUBASE היקר, שלזכותו נזקפים כאן עשרות מודולים ומדריכים שהוא פיתח ומפתח, וחשבתי שהגיע הזמן לפתוח פוסט עם כל המודולים שלו לטובת כלל המשתמשים.

      שימו לב השירשור מתעדכן.
      לתגובות

      מדריכים:
      הדרך הפשוטה ביותר לניתוב המאזין לשלוחה פנימית במערכת אחרת!!!
      רישום לצינתוקים אישיים בצורה המהירה ביותר וללא API!
      מדריך מלא | הקמת שלוחת וירטואל פלוס (שיחות יוצאות ונכנסות) ב-IVR2
      מדריך | הוספת ערכים לשני קבצים בהזנה אחת ע"י מודול הוספת ערכים (add_id_to_list)

      פיתוחים:
      הקלטת הודעה אישית בעת שמיעת הודעה ללא הקשת המספר!! (פיתוח פרטי)
      צפייה במספרים שהוזמנו לרשימת צינתוקים באתר (Tampermonkey)!!!
      פיתוח פרטי | לייק ודיסלייק בהשמעת קבצים
      פיתוח פרטי | ניתוב למספר בהשמע"ק לפי הקשה בהקלטה
      מודול "הקלטה וניתוב חכם Pro" – המדריך המלא והמשודרג!

      לתגובות

      פורסם בהסברים מסודרים ממשתמשים
      א
      אA
    • פרוייקט שיתופי: בניית קובץ להגדרת הגדרות במערכת

      בניתי קובץ שבעזרתו יהיה ניתן להגדיר הגדרות במערכות כמו שיש באתר הישן ע"י שאלות שהאתר מציג.
      מכיון שהכנסת ההגדרות כולל עבודה מרובה הגדרתי אפשרות להוסיף שאלות להגדרות לטופס כך שבעבודה שיתופית יהיה ניתן ליצור קובץ מושלם.

      הסבר קטן על הוספת שאלות והגדרות לקובץ:
      כל מודול מורכב מהגדרה ראשית שהיא ההגדרה הבסיסית של השלוחה , ותחתיה הגדרות נוספות שניתנות להגדרה.
      כל הגדרה נוספת מורכבת משאלה שתוצג למשתמש לדו' "להגדרת תפריט עם יותר מספרה אחת"
      שעליה המאזין יענה בכן או לא, ומהגדרה שתתווסף לקובץ ההגדרות במידה והמשתמש ילחץ על כן.
      ההגדרות מתקבצות למקום אחד ובלחיצה על העלאה למערכת הם יוכנסו לקובץ הנבחר.

      בנוסף יש את האפשרות של שאלה נלווית, לדו' בשאלה "האם להגדיר תפריט בעל יותר מ9 שלוחות"
      במידה והמשתמש בחר ב"כן" תיפתח שאלה נלווית שתשאל אותו "כמה ספרות ימנה התפריט" ותחתיה שורה בה ימלא את מספר הספרות.
      בנוסף בשאלה נלווית יש להזין את המילה שתשתנה בהתאם למה שהוכנס בשדה, לדו' בהגדרה של תפריט מרובה שלוחות ההגדרה הבסיסית היא "digits=2" והערך אמור לשנות את הערך "2" למספר שהוזן, ולכן במילה שתשתנה יש לכתוב "2".

      בנוסף כדי שנוכל ליצור קובץ עם כל ההגדרות שיתווספו אצל כל אחד, ניתן להוריד את המאגר שלכם למחשב ולהעלות אותו לפורום ואני יחבר את כל המאגרים יחד ויעדכן בפוסט..
      או לחילופין להוריד כאקסל ולהעלות לשיטס הבא
      https://docs.google.com/spreadsheets/d/1gLEV0FB8jXBtkBd14Z2DrIj7vAXwLa7HmKQ2jn4_TH0******/edit?hl=he&gid=0#gid=0
      (להוריד כוכביות)
      אם אתם בנטפרי אז הם חוסמים את השיטס בטענה של קובץ שפורסם לציבור.
      מה שצריך זה לייצא כנתונים ולהעלות לכאן ואני אכניס.
      או שמישהו ישלח פנייה לנטפרי שיפתחו לטובת הציבור.

      בנוסף ניתן כבר להשתמש בהגדרות שכבר נמצאות להגדרה במערכת שלכם.

      אשמח שכל מי שיש לו מעט זמן פנוי שיכניס כמה הגדרות כך שנוכל ליצור קובץ שיכיל כמה שיותר הגדרות לתועלת כולנו.
      אשמח גם להערות והארות בנושא.

      הקובץ להורדה
      הגדרת שלוחות.html

      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • RE: גיבוי המערכת שלכם במחשב האישי בלחיצת כפתור!!!

      עידכון!

      קוד חדש עם הורדה בשיטה שתופסת פחות זיכרון (מיוחד למערכות גדולות), וכן עם מד מהירות ההורדה.

      הקוד המעודכן

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>גיבוי מערכות ישירות למחשב האישי</title>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
          <style>
              body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 20px; background-color: #f4f7f6; text-align: right; direction: rtl; }
              .container { max-width: 1100px; margin: auto; background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
              .section { border: 1px solid #e0e0e0; padding: 15px; margin-bottom: 15px; border-radius: 8px; background: #fafafa; }
              #logArea { 
                  background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; 
                  height: 250px; overflow-y: auto; font-family: 'Consolas', monospace; font-size: 13px; margin-top: 10px;
              }
              .log-info { color: #4fc3f7; }
              .log-success { color: #00ff00; font-weight: bold; }
              .log-error { color: #ff5252; }
              button { padding: 10px 20px; background: #3498db; color: white; border: none; cursor: pointer; border-radius: 5px; font-weight: bold; transition: background 0.3s; }
              button:hover { background: #2980b9; }
              button:disabled { background: #bdc3c7; }
              input { padding: 10px; margin: 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
              .stats-bar { font-weight: bold; color: #2c3e50; margin-bottom: 5px; font-size: 16px; background: #e8f4fd; padding: 10px; border-radius: 5px; display: inline-block; }
              .progress-container { background: #e0e0e0; border-radius: 20px; height: 25px; margin: 15px 0; overflow: hidden; display: none; }
              .progress-bar { width: 0%; height: 100%; background: linear-gradient(90deg, #2ecc71, #27ae60); transition: width 0.3s; color: white; text-align: center; line-height: 25px; font-weight: bold; }
              table { width: 100%; border-collapse: collapse; margin-top: 15px; }
              th, td { border: 1px solid #ddd; padding: 10px; text-align: right; }
              tr:nth-child(even) { background: #f9f9f9; }
          </style>
      </head>
      <body>
      
      <div class="container">
          <h2>גיבוי מערכות ישירות למחשב האישי</h2>
          
          <div class="section">
              <strong>⚙️ הגדרות:</strong>
              סרוק שלוחות בעלות <input type="number" id="digitCount" style="width:50px;" value="2"> ספרות.
              שם קובץ ZIP: <input type="text" id="fileNameInput" value="backup_ivr">
          </div>
      
          <div class="section">
              <strong>1. פרטי המערכת</strong><br>
              <input type="text" id="srcToken" style="width:350px;" placeholder="הכנס טוקן...">
              <input type="text" id="srcPath" value="/">
              <button onclick="loadFolder(document.getElementById('srcPath').value)">טען רשימת קבצים</button>
          </div>
      
          <div class="section">
              <strong>2. ביצוע הורדה</strong><br>
              <div id="statsBar" class="stats-bar">מהירות: 0 KB/s | סה"כ ירד: 0 MB</div><br><br>
              <button id="downloadBtn" onclick="startDownload()" disabled style="background:#27ae60;">בחר מיקום ושמור כקובץ ZIP</button>
              
              <div class="progress-container" id="progContainer">
                  <div id="progBar" class="progress-bar">0%</div>
              </div>
              <div id="logArea">מוכן...</div>
          </div>
      
          <div id="fileArea" style="display:none;">
              <table>
                  <thead>
                      <tr style="background:#eee">
                          <th style="width: 40px;"><input type="checkbox" id="masterCheck" checked onclick="toggleAll(this)"></th>
                          <th>סוג</th>
                          <th>שם</th>
                      </tr>
                  </thead>
                  <tbody id="fileTableBody"></tbody>
              </table>
          </div>
      </div>
      
      <script>
      let currentViewPath = "/";
      let totalBytes = 0;
      let startTime = 0;
      
      // פונקציית "נשימה" - משחררת את הזיכרון של הדפדפן ומאפשרת לממשק להתעדכן
      const breathe = () => new Promise(resolve => setTimeout(resolve, 10));
      
      function addLog(msg, type = '') {
          const logArea = document.getElementById('logArea');
          const div = document.createElement('div');
          div.className = `log-${type}`;
          div.innerHTML = `[${new Date().toLocaleTimeString()}] ${msg}`;
          logArea.appendChild(div);
          logArea.scrollTop = logArea.scrollHeight;
      }
      
      function updateStats(newBytes) {
          totalBytes += newBytes;
          const duration = (Date.now() - startTime) / 1000;
          const speed = duration > 0 ? (totalBytes / 1024 / duration).toFixed(2) : 0;
          const mb = (totalBytes / 1024 / 1024).toFixed(2);
          document.getElementById('statsBar').innerText = `מהירות: ${speed} KB/s | סה"כ ירד: ${mb} MB`;
      }
      
      async function loadFolder(path) {
          const token = document.getElementById('srcToken').value;
          const digits = parseInt(document.getElementById('digitCount').value) || 2;
          if (!token) { alert("נא להזין טוקן"); return; }
          
          currentViewPath = path;
          const tbody = document.getElementById('fileTableBody');
          tbody.innerHTML = '<tr><td colspan="3">סורק שלוחות...</td></tr>';
      
          try {
              const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${path}`);
              const data = await res.json();
              let filesMap = new Map();
              if (data.files) data.files.forEach(f => filesMap.set(f.name, f));
      
              const maxRange = Math.pow(10, digits) - 1;
              const scanPromises = [];
              for (let i = 0; i <= maxRange; i++) {
                  const n = i.toString();
                  if (filesMap.has(n)) continue;
                  scanPromises.push(
                      fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${path}/${n}/ext.ini`)
                      .then(r => r.json())
                      .then(d => { if (d.contents !== undefined) filesMap.set(n, { name: n, fileType: "DIR" }); })
                  );
              }
              await Promise.all(scanPromises);
      
              tbody.innerHTML = '';
              filesMap.forEach(f => {
                  tbody.insertAdjacentHTML('beforeend', `
                      <tr>
                          <td><input type="checkbox" class="file-check" data-name="${f.name}" data-type="${f.fileType}" checked></td>
                          <td>${f.fileType === "DIR" || !isNaN(f.name) ? "שלוחה" : "קובץ"}</td>
                          <td>${f.name}</td>
                      </tr>`);
              });
              document.getElementById('fileArea').style.display = 'block';
              document.getElementById('downloadBtn').disabled = false;
              addLog("סריקה הושלמה.", "info");
          } catch (e) { addLog("שגיאה בסריקה", "error"); }
      }
      
      async function downloadRecursive(token, path, name, type, zipFolder) {
          const sPath = `${path}/${name}`.replace(/\/+/g, '/');
          const digits = parseInt(document.getElementById('digitCount').value) || 2;
      
          if (type === "DIR" || !isNaN(name)) {
              addLog(`מעבד שלוחה: ${sPath}`, "info");
              const newFolder = zipFolder.folder(name);
              
              try {
                  const iniRes = await fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/ext.ini`);
                  const iniData = await iniRes.json();
                  if (iniData.contents !== undefined) {
                      newFolder.file("ext.ini.txt", iniData.contents);
                      updateStats(new TextEncoder().encode(iniData.contents).length);
                  }
              } catch(e) {}
      
              const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${sPath}`);
              const data = await res.json();
              let children = data.files || [];
      
              const subScan = [];
              for(let i=0; i <= Math.pow(10, digits)-1; i++) {
                  const n = i.toString();
                  if(!children.find(c => c.name === n)) {
                      subScan.push(fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/${n}/ext.ini`).then(r => r.json()).then(d => { if(d.contents !== undefined) children.push({name: n, fileType: "DIR"}); }));
                  }
              }
              await Promise.all(subScan);
      
              for (const f of children) {
                  if (f.name === "ext.ini") continue;
                  await downloadRecursive(token, sPath, f.name, f.fileType, newFolder);
                  await breathe(); // הפסקה קצרה לשחרור זיכרון ברמת התיקייה
              }
          } else {
              try {
                  let finalName = name;
                  if (!name.toLowerCase().match(/\.(mp3|wav|txt)$/)) finalName += ".txt";
                  
                  const dl = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${token}&path=ivr2:${sPath}`);
                  const arrayBuffer = await dl.arrayBuffer();
                  updateStats(arrayBuffer.byteLength);
                  zipFolder.file(finalName, arrayBuffer);
                  
                  // שחרור מהיר של ה-Buffer מהזיכרון הראשי
                  await breathe(); 
              } catch(e) { addLog(`שגיאה ב-${name}`, "error"); }
          }
      }
      
      async function startDownload() {
          const token = document.getElementById('srcToken').value;
          const userFileName = document.getElementById('fileNameInput').value || 'backup';
          const selected = Array.from(document.querySelectorAll('.file-check:checked'));
          if (selected.length === 0) return;
      
          let fileHandle = null;
          if ('showSaveFilePicker' in window) {
              try {
                  fileHandle = await window.showSaveFilePicker({
                      suggestedName: `${userFileName}.zip`,
                      types: [{ description: 'ZIP Archive', accept: {'application/zip': ['.zip']} }]
                  });
              } catch (e) { return; }
          }
      
          const zip = new JSZip();
          document.getElementById('downloadBtn').disabled = true;
          document.getElementById('progContainer').style.display = 'block';
          totalBytes = 0;
          startTime = Date.now();
      
          for (let i = 0; i < selected.length; i++) {
              await downloadRecursive(token, currentViewPath, selected[i].dataset.name, selected[i].dataset.type, zip);
              let p = Math.round(((i + 1) / selected.length) * 100);
              document.getElementById('progBar').style.width = p + '%';
              document.getElementById('progBar').innerText = p + '%';
              await breathe(); // הפסקה קריטית בין שלוחות ראשיות
          }
      
          addLog("יוצר ZIP סופי... במערכות גדולות זה יכול לקחת כמה דקות", "success");
          
          // יצירת ה-ZIP עם אופטימיזציה למהירות על פני דחיסה במערכות ענק
          const content = await zip.generateAsync({
              type: "blob",
              compression: "STORE", // STORE אומר "אל תדחוס", זה הרבה יותר מהר וחוסך CPU
              streamFiles: true
          });
      
          if (fileHandle) {
              const writable = await fileHandle.createWritable();
              await writable.write(content);
              await writable.close();
          } else {
              const link = document.createElement('a');
              link.href = URL.createObjectURL(content);
              link.download = `${userFileName}.zip`;
              link.click();
          }
      
          addLog("✅ הגיבוי הסתיים בהצלחה!", "success");
          document.getElementById('downloadBtn').disabled = false;
      }
      
      function toggleAll(source) {
          document.querySelectorAll('.file-check').forEach(cb => cb.checked = source.checked);
      }
      </script>
      </body>
      </html>
      
      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • קבצים ותוכנות לשימוש במערכות ימות המשיח

      לתגובות

      לאור כך שבתקופה האחרונה הרבה משתמשים יקרים כאן בפורום העלו לטובת כלל המשתמשים קבצי HTML שמקילים מאוד את השימוש במערכות ללא צורך להיכנס למערכת בכל פעם, חשבתי שכדאי לקבץ את כל הקבצים לשירשור אחד, כך יוכלו למצוא אותם בקלות.
      אשמח שכל מי שנתקל בקבצים שעדיין לא הועלו כאן שיעדכן בשירשור עם הקרדיט למפתח.

      בנוסף אני אעדכן את הקודים של הקבצים.
      כדי לפותחם כקובץ יש להעתיק את הטקסט לקובץ טקסט פשוט >קובץ>שמור בשם,ולשמור בשם כל שהוא, ובסוף נקודה (.) ואז html. בנוסף בסוג קובץ לבחור All Files ואז שמור, וזהו.

      סרטון לדוגמא
      סרטון לדוגמא

      ניתן להוריד את התוכנה הזו ולהכניס לתוכה את קובץ הhtml והיא תיצור ממנו תוכנה ניידת.
      בונה_התוכנות.zip

      רשימת הקבצים: (מתעדכן)

      קובץ להוספת והגדרת שלוחות במערכות (מתעדכן)
      קובץ להעלאת קבצי שמע למערכת
      קובץ להוספת נקודות למאזינים
      תוכנה להורדה של רשימת הצינתוקים למחשב
      קובץ לצפייה בדוחות של קבלת נתונים
      קובץ ליצירת שלוחות API
      קובץ להורדת קבצים מהמערכת (קבצי wav, tts, ini, html) כולל אפשרות להמרה + אפשרות להורדת קבצים מרובים
      קובץ להעלאה והורדה של כל סוגי הקבצים למערכות כולל קבצי ini - עם אפשרות להפעלת צינתוק בסיום העלאה!
      קובץ להפעלת קמפיין - הנכנס לשלוחה במערכת
      קובץ לניהול הקבצים במערכת
      ממשק ליצירת, ניהול, והפעלת קמפיין
      הוספת טקסט למספר קבצים במערכת
      ממשק להתקנת תא קולי מתקדם על המערכת שלכם בהכנסת מספר וסיסמא בלבד!
      קובץ להעברת שלוחות וקבצים בין מערכות (גם שלוחות שלמות!)
      קובץ להעלאת תקיות וקבצים למערכת - בצורת עץ שלוחות
      קובץ לגיבוי המערכת במחשב האישי שלכם!
      ניהול קבלת נתונים מקצועי - הקוד המשודרג + כולל ריענון אוטומטי + אפשרות שיתוף (הכוללת אפשרות לצפות בטבלה ולהוריד כאקסל ללא אפשרות לגעת בהגדרות)
      קובץ לשינוי פרטי הקובץ בהשמעת קבצים
      קובץ לפתיחת שלוחת טרוויה
      קובץ לניתוח נתוני המערכת


      קובץ להורדת קבצים מהמערכת (קבצי wav, tts, ini, html) כולל אפשרות להמרה + אפשרות להורדת קבצים מרובים

      הקוד מצורף בספויילר

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>ממשק הורדת קובץ</title>
          <style>
              body {
                  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                  padding: 20px;
                  background-color: #e9ecef;
                  text-align: right;
                  direction: rtl;
              }
              .container {
                  background-color: #ffffff;
                  padding: 30px;
                  border-radius: 12px;
                  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
                  max-width: 400px;
                  margin: 40px auto;
              }
              h2 {
                  font-size: 24px;
                  color: #343a40;
                  margin-bottom: 25px;
                  text-align: center;
                  border-bottom: 2px solid #007BFF;
                  padding-bottom: 10px;
              }
              label, input, button, select {
                  display: block;
                  width: 100%;
                  margin-bottom: 15px;
                  box-sizing: border-box;
              }
              label {
                  font-weight: 600;
                  color: #495057;
                  margin-bottom: 5px;
              }
              input[type="text"], select {
                  padding: 12px;
                  border: 1px solid #ced4da;
                  border-radius: 6px;
                  transition: border-color 0.3s;
              }
              input[type="text"]:focus, select:focus {
                  border-color: #007BFF;
                  outline: none;
              }
              button {
                  background-color: #007BFF;
                  color: white;
                  padding: 12px;
                  border: none;
                  border-radius: 6px;
                  cursor: pointer;
                  font-size: 18px;
                  font-weight: bold;
                  margin-top: 20px;
                  transition: background-color 0.3s;
              }
              button:hover {
                  background-color: #0056b3;
              }
              #conversionOptions {
                  display: none; 
                  margin-top: -5px;
              }
              #iniWarning {
                  color: #721c24;
                  background-color: #f8d7da;
                  border: 1px solid #f5c6cb;
                  padding: 10px;
                  border-radius: 5px;
                  margin-bottom: 15px;
                  display: none;
              }
              /* סגנון הודעת שגיאה כללית */
              #generalError {
                  color: #721c24;
                  background-color: #f8d7da;
                  border: 1px solid #f5c6cb;
                  padding: 10px;
                  border-radius: 5px;
                  margin-top: 15px;
                  font-weight: bold; 
                  display: none;
                  text-align: center;
              }
          </style>
      </head>
      <body>
      
      <div class="container">
          <h2>🔗 ממשק הורדת קובץ</h2>
          
          <label for="token">טוקן (Token):</label>
          <input type="text" id="token" placeholder="יש ליצור טוקן בלשונית אבטחה" oninput="saveInputValues()">
      
          <label for="path">נתיב השלוחה (לדוגמה: 1/2):</label>
          <input type="text" id="path" placeholder="נתיב השלוחה" oninput="saveInputValues()">
      
          <label for="filename">שם הקובץ (לדוגמה: 001):</label>
          <input type="text" id="filename" placeholder="שם הקובץ" oninput="saveInputValues()">
          
          <label for="filetype">סוג הקובץ:</label>
          <select id="filetype" onchange="toggleConversion(); saveInputValues();">
              <option value="wav">wav</option>
              <option value="tts">tts</option>
              <option value="ini">ini</option>
              <option value="ymgr">ymgr</option>
          </select>
          
          <div id="iniWarning">
              ⚠️ **שים לב: קובץ INI**
              <ul>
                  <li>הקובץ יורד כקובץ **INI**. לאחר ההורדה, ייתכן שתידרש **לשנות את סיומת הקובץ ל-**.txt** באופן ידני** כדי לפתוח אותו כקובץ טקסט רגיל.</li>
                  <li>בשל סוג הקובץ, תוכנת האנטי-וירוס או הדפדפן **עלולים למנוע את הורדת הקובץ** או להציג אזהרה.</li>
              </ul>
          </div>
      
          <div id="conversionOptions">
              <label for="conversionFormat">פורמט המרה (עבור ymgr):</label>
              <select id="conversionFormat" onchange="saveInputValues()">
                  <option value="">ללא המרה (ymgr)</option>
                  <option value="csv">אקסל (xlsx)</option> 
                  <option value="html">HTML</option>
              </select>
          </div>
      
          <button onclick="generateAndDownload()">הורד קובץ</button>
          
          <div id="generalError"></div> 
      
      </div>
      
      <script>
          // **פונקציה לשמירת ערכי השדות ב-localStorage**
          function saveInputValues() {
              localStorage.setItem('download_token', document.getElementById('token').value);
              localStorage.setItem('download_path', document.getElementById('path').value);
              localStorage.setItem('download_filename', document.getElementById('filename').value);
              localStorage.setItem('download_filetype', document.getElementById('filetype').value);
              localStorage.setItem('download_conversionFormat', document.getElementById('conversionFormat').value);
          }
      
          // **פונקציה לטעינת ערכי השדות מ-localStorage**
          function loadInputValues() {
              document.getElementById('token').value = localStorage.getItem('download_token') || '';
              document.getElementById('path').value = localStorage.getItem('download_path') || '';
              document.getElementById('filename').value = localStorage.getItem('download_filename') || '';
              
              // טעינת שדות Select
              const filetype = localStorage.getItem('download_filetype');
              if (filetype) {
                  document.getElementById('filetype').value = filetype;
              }
      
              const conversionFormat = localStorage.getItem('download_conversionFormat');
              if (conversionFormat) {
                  document.getElementById('conversionFormat').value = conversionFormat;
              }
              
              // נפעיל את toggleConversion כדי לוודא שהתצוגה מתאימה לערכים שנטענו
              toggleConversion();
          }
      
      
          // פונקציה לבניית ה-URL
          function buildUrl(token, path, filename, filetype, conversionFormat) {
              const downloadUrlTemplate = "https://www.call2all.co.il/ym/api/DownloadFile?token=טוקן&path=ivr2:נתיב השלוחה/שם הקובץ.סיומת";
              const renderUrlTemplate = "https://www.call2all.co.il/ym/api/RenderYMGRFile?token=טוקן&wath=ivr2:נתיב_השלוחה/שם_הקובץ.סיומת&convertType=סוג_ההמרה";
              
              let finalUrl = "";
              let downloadFilename = "";
      
              if (filetype === 'ymgr' && conversionFormat !== '') {
                  let conversionType = conversionFormat;
                  
                  finalUrl = renderUrlTemplate
                      .replace("טוקן", encodeURIComponent(token))
                      .replace("נתיב_השלוחה", encodeURIComponent(path))
                      .replace("שם_הקובץ", encodeURIComponent(filename))
                      .replace("סיומת", encodeURIComponent(filetype)) 
                      .replace("סוג_ההמרה", encodeURIComponent(conversionType)); 
                  
                  downloadFilename = (conversionType === 'csv') ? `${filename}.xlsx` : `${filename}.${conversionType}`;
      
              } else {
                  // כולל ini, wav, tts, ו-ymgr ללא המרה
                  finalUrl = downloadUrlTemplate
                      .replace("טוקן", encodeURIComponent(token))
                      .replace("נתיב השלוחה", encodeURIComponent(path))
                      .replace("שם הקובץ", encodeURIComponent(filename))
                      .replace("סיומת", encodeURIComponent(filetype));
                      
                  downloadFilename = (filetype === 'ini') ? `${filename}.ini` : `${filename}.${filetype}`;
              }
              
              return { finalUrl, downloadFilename };
          }
      
          // פונקציה להצגה/הסתרה של אזהרות והמרות
          function toggleConversion() {
              const fileType = document.getElementById('filetype').value;
              const conversionOptions = document.getElementById('conversionOptions');
              const iniWarning = document.getElementById('iniWarning');
              
              conversionOptions.style.display = (fileType === 'ymgr') ? 'block' : 'none';
              iniWarning.style.display = (fileType === 'ini') ? 'block' : 'none';
          }
      
          // פונקציה להסתרת כל הודעות השגיאה
          function hideAllErrors() {
              document.getElementById('generalError').style.display = 'none';
          }
      
          // הפונקציה הראשית (עכשיו אסינכרונית כדי לאפשר fetch)
          async function generateAndDownload() {
              const token = document.getElementById('token').value;
              const path = document.getElementById('path').value;
              const filename = document.getElementById('filename').value;
              const filetype = document.getElementById('filetype').value;
              const conversionFormat = document.getElementById('conversionFormat').value;
      
              const errorElement = document.getElementById('generalError');
              
              // הסתרת שגיאות קודמות
              hideAllErrors();
      
              if (!token || !path || !filename || !filetype) {
                  alert("אנא מלא את כל השדות הנדרשים.");
                  return;
              }
      
              const { finalUrl, downloadFilename } = buildUrl(token, path, filename, filetype, conversionFormat);
      
              try {
                  // 1. נבצע fetch ל-API כדי לבדוק את התוכן
                  const response = await fetch(finalUrl);
                  const content = await response.text();
      
                  let errorMessage = "";
                  // ההודעה המקורית: {"yemotAPIVersion":7,"responseStatus":"EXCEPTION","message":"IllegalStateException(session token is invalid)"}
                  const illegalStateErrorIdentifier = `"IllegalStateException(session token is invalid)"`; 
                  
                  // 2. בדיקת שגיאות API ספציפיות
                  
                  // בדיקת שגיאת טוקן ספציפית
                  if (content.includes(illegalStateErrorIdentifier)) {
                      errorMessage = "❌ שגיאת טוקן חמור: הטוקן שהוזן אינו חוקי.";
                  }
                  // בדיקת שגיאת טוקן כללית
                  else if (content.includes("Error: Token not found or wrong")) {
                      errorMessage = "❌ שגיאת טוקן:הטוקן שהוזן אינו נכון.";
                  }
                  
                  // בדיקת קובץ/נתיב לא קיים (הסרת כוביות והדגשה)
                  else if (content.includes("Requested file does not exist")) {
                      errorMessage = "❌ הקובץ אינו קיים: אנא ודא שגם הנתיב (" + path + ") וגם שם הקובץ (" + filename + ") נכונים.";
                  }
                  
      
                  // 3. טיפול בשגיאות שנמצאו
                  if (errorMessage) {
                      errorElement.innerHTML = errorMessage;
                      errorElement.style.display = 'block';
                      return;
                  }
                  
                  // 4. אם אין שגיאה בתוכן, מפעילים הורדה
                  const link = document.createElement('a');
                  link.href = finalUrl;
                  link.download = downloadFilename;
      
                  document.body.appendChild(link);
                  link.click();
                  document.body.removeChild(link);
      
              } catch (error) {
                  // שגיאת רשת או CORS
                  errorElement.innerHTML = "⚠️ **שגיאה כללית/רשת:** לא ניתן לאמת את קיום הקובץ (יתכן חסימת CORS). אנא ודא את הנתונים.";
                  errorElement.style.display = 'block';
              }
          }
      
          document.addEventListener('DOMContentLoaded', () => {
              // טוען את הערכים השמורים בהתחלה
              loadInputValues();
              // מוודא שהתצוגה נכונה לפי הערכים שנטענו (מופעל גם ב-loadInputValues)
              // toggleConversion();
          });
      </script>
      
      </body>
      </html>
      

      קרדיט: @אa


      קובץ להעלאה והורדה של כל סוגי הקבצים למערכות כולל קבצי ini - עם אפשרות להפעלת צינתוק בסיום העלאה!

      הקוד מצורף בספויילר

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מערכת ניהול קבצים מאוחדת - ימות המשיח</title>
          <style>
              body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f7f6; margin: 0; padding: 20px; direction: rtl; }
              .card { background: white; max-width: 900px; margin: auto; padding: 30px; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); }
              h2 { color: #2c3e50; text-align: center; margin-bottom: 25px; border-bottom: 2px solid #007BFF; padding-bottom: 10px; }
              
              .type-selector { display: flex; justify-content: center; gap: 15px; margin-bottom: 25px; background: #ecf0f1; padding: 10px; border-radius: 10px; }
              .type-option { cursor: pointer; padding: 10px 20px; border-radius: 8px; font-weight: bold; transition: 0.3s; flex: 1; text-align: center; }
              .type-option.active { background: #007BFF; color: white; }
              
              .form-group { margin-bottom: 20px; }
              label { display: block; margin-bottom: 8px; font-weight: 600; color: #495057; }
              input[type="text"], input[type="number"], input[type="file"], textarea { width: 100%; padding: 12px; border: 1px solid #ced4da; border-radius: 8px; box-sizing: border-box; font-size: 14px; }
              
              .ini-container { display: flex; gap: 20px; margin-bottom: 15px; }
              .ini-box { flex: 1; display: flex; flex-direction: column; }
              .ini-box textarea { height: 250px; resize: none; }
              .ini-box span { font-weight: bold; margin-bottom: 5px; color: #2c3e50; display: block; }
      
              .path-row { display: flex; gap: 8px; align-items: center; }
              .prefix { background: #dfe6e9; padding: 12px; border-radius: 8px; border: 1px solid #bdc3c7; font-weight: bold; }
              
              .progress-container { width: 100%; background-color: #eee; border-radius: 10px; margin: 20px 0; display: none; overflow: hidden; border: 1px solid #ccc; }
              .progress-bar { width: 0%; height: 25px; background-color: #28a745; text-align: center; line-height: 25px; color: white; font-weight: bold; transition: width 0.4s ease; }
      
              .tzintuk-section { border: 1px solid #ddd; padding: 15px; border-radius: 8px; background: #f9f9f9; margin-bottom: 20px; }
              .btn-container { display: flex; gap: 10px; margin-top: 10px; }
              .submit-btn { flex: 2; background-color: #28a745; color: white; border: none; padding: 15px; font-size: 16px; cursor: pointer; border-radius: 8px; font-weight: bold; transition: 0.3s; }
              .submit-btn:disabled { background-color: #6c757d; cursor: not-allowed; }
              .list-btn { flex: 1; background-color: #007BFF; color: white; border: none; padding: 15px; font-size: 16px; cursor: pointer; border-radius: 8px; font-weight: bold; }
              
              .load-btn-dynamic { background-color: #28a745; color: white; border: none; padding: 8px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: bold; margin-top: 10px; align-self: flex-start; transition: background-color 0.3s; min-width: 140px; }
              .load-btn-loading { background-color: #007BFF !important; }
      
              .hidden { display: none; }
              #fileListContainer { margin-top: 25px; border-top: 2px solid #eee; padding-top: 15px; display: none; }
              table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
              th, td { border: 1px solid #dee2e6; padding: 12px; text-align: center; }
          </style>
      </head>
      <body>
      
      <div class="card">
          <h2>🔗 ניהול קבצי ימות המשיח</h2>
          
          <div class="type-selector">
              <div id="opt-file" class="type-option active" onclick="switchMode('file')">העלאת קובץ שמע</div>
              <div id="opt-text" class="type-option" onclick="switchMode('text')">העלאת קובץ טקסט</div>
              <div id="opt-ini" class="type-option" onclick="switchMode('ini')">העלאת קובץ INI</div>
          </div>
      
          <form id="uploadForm">
              <div class="form-group">
                  <label>טוקן אישי (Token):</label>
                  <input type="text" id="token" required placeholder="הזן טוקן כאן">
              </div>
      
              <div class="form-group">
                  <label>שלוחות יעד (הפרד בפסיקים עבור מספר שלוחות):</label>
                  <div class="path-row">
                      <span class="prefix">ivr2:</span>
                      <input type="text" id="folder" placeholder="למשל: 5, 10, 15/1" required>
                  </div>
              </div>
      
              <div id="progressContainer" class="progress-container">
                  <div id="progressBar" class="progress-bar">0%</div>
              </div>
      
              <div id="file-section">
                  <div class="form-group">
                      <label>בחר קובץ:</label>
                      <input type="file" id="fileInput">
                  </div>
                  <div class="form-group">
                      <label>שם קובץ (אופציונלי):</label>
                      <input type="text" id="fileNameAudio" placeholder="למשל 000">
                  </div>
              </div>
      
              <div id="text-section" class="hidden">
                  <div class="form-group">
                      <label>הטקסט להעלאה:</label>
                      <textarea id="textContent" style="height:150px;" placeholder="כתוב טקסט"></textarea>
                  </div>
                  <div class="form-group">
                      <label>שם קובץ (חובה):</label>
                      <input type="text" id="fileNameText" placeholder="למשל 001.tts">
                  </div>
              </div>
      
              <div id="ini-section" class="hidden">
                  <div class="form-group">
                      <label>שם קובץ (ללא סיומת):</label>
                      <input type="text" id="fileNameIni" value="ext" placeholder="למשל ext">
                  </div>
      
                  <div class="ini-container">
                      <div class="ini-box">
                          <span>תוכן הקובץ הקיים</span>
                          <textarea id="iniContentExisting" readonly placeholder="התוכן הקיים יופיע כאן"></textarea>
                          <button type="button" id="loadIniBtn" onclick="fetchExistingText()" class="load-btn-dynamic">טען תוכן קיים</button>
                      </div>
                      <div class="ini-box">
                          <span>טען תוכן להעלאה</span>
                          <textarea id="iniContentNew" placeholder="כתוב טקסט להעלאה..."></textarea>
                      </div>
                  </div>
              </div>
      
              <div id="tzintuk-wrapper" class="tzintuk-section">
                  <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
                      <input type="checkbox" id="use-tzintuk" style="width: 18px; height: 18px;" 
                             onchange="document.getElementById('tz-fields').style.display = this.checked ? 'block' : 'none'">
                      <span style="font-weight: bold; color: #2c3e50;">הפעל צינתוק בסיום ההעלאה</span>
                  </label>
      
                  <div id="tz-fields" style="display: none; margin-top: 15px; border-top: 1px solid #eee; padding-top: 10px;">
                      <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
                          <div>
                              <label style="font-size: 13px;">זיהוי יוצא (callerId):</label>
                              <input type="text" id="tz-caller" placeholder="הכנס את הזיהוי היוצא">
                          </div>
                          <div>
                              <label style="font-size: 13px;">זמן (9-16 שניות):</label>
                              <input type="number" id="tz-time" value="9" min="9" max="16">
                          </div>
                      </div>
                      <div>
                          <label style="font-size: 13px;">מספר רשימת הצינתוקים:</label>
                          <input type="text" id="tz-phones" placeholder="הכנס את מספר רשימת הצינתוקים, למספר רשימות יש להפריד עם פסיק">
                      </div>
                  </div>
              </div>
      
              <div class="btn-container">
                  <button type="button" id="mainSubmitBtn" onclick="executeUpload()" class="submit-btn">בצע העלאה</button>
                  <button type="button" onclick="listFiles()" class="list-btn">הצג קבצים בשלוחה</button>
              </div>
          </form>
      
          <div id="fileListContainer">
              <table id="filesTable">
                  <thead>
                      <tr>
                          <th>שם קובץ</th>
                          <th>פעולות</th>
                      </tr>
                  </thead>
                  <tbody id="filesBody"></tbody>
              </table>
          </div>
      </div>
      
      <script>
          let currentMode = 'file';
      
          function switchMode(mode) {
              currentMode = mode;
              document.getElementById('opt-file').classList.toggle('active', mode === 'file');
              document.getElementById('opt-text').classList.toggle('active', mode === 'text');
              document.getElementById('opt-ini').classList.toggle('active', mode === 'ini');
              
              document.getElementById('file-section').classList.toggle('hidden', mode !== 'file');
              document.getElementById('text-section').classList.toggle('hidden', mode !== 'text');
              document.getElementById('ini-section').classList.toggle('hidden', mode !== 'ini');
      
              // הסתרת אפשרות הצינתוק אם אנחנו במצב INI
              const tzWrapper = document.getElementById('tzintuk-wrapper');
              if (mode === 'ini') {
                  tzWrapper.style.display = 'none';
              } else {
                  tzWrapper.style.display = 'block';
              }
          }
      
          async function fetchExistingText() {
              const token = document.getElementById('token').value;
              const folder = document.getElementById('folder').value.split(',')[0].trim();
              let fileName = document.getElementById('fileNameIni').value.trim() || "ext";
              const btn = document.getElementById('loadIniBtn');
              if (!token || !folder) { alert("נא למלא טוקן ושלוחה לפני הטעינה"); return; }
              btn.innerText = "טוען תוכן קובץ...";
              btn.classList.add('load-btn-loading'); btn.disabled = true;
              if (!fileName.toLowerCase().endsWith('.ini')) fileName += ".ini";
              const fullPath = `ivr2:${folder}/${fileName}`;
              try {
                  const url = `https://www.call2all.co.il/ym/api/GetTextFile?token=${encodeURIComponent(token)}&what=${encodeURIComponent(fullPath)}`;
                  const res = await fetch(url);
                  const data = await res.json();
                  document.getElementById('iniContentExisting').value = data.responseStatus === "OK" ? (data.contents || "קובץ ריק") : "קובץ לא נמצא";
              } catch (e) { alert("שגיאת תקשורת"); } finally {
                  btn.innerText = "טען תוכן קיים"; btn.classList.remove('load-btn-loading'); btn.disabled = false;
              }
          }
      
          async function listFiles() {
              const token = document.getElementById('token').value;
              const folder = document.getElementById('folder').value.split(',')[0].trim();
              const tbody = document.getElementById('filesBody');
              if (!token || !folder) { alert("מלא טוקן ושלוחה (הראשונה)"); return; }
              try {
                  const url = `https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${folder}`;
                  const res = await fetch(url);
                  const data = await res.json();
                  if (data.responseStatus === "OK") {
                      tbody.innerHTML = "";
                      data.files.forEach(f => {
                          tbody.innerHTML += `<tr><td>${f.name}</td><td><button onclick="alert('נתיב: ivr2:${folder}/${f.name}')">פרטים</button></td></tr>`;
                      });
                      document.getElementById('fileListContainer').style.display = "block";
                  }
              } catch (e) { alert("שגיאת תקשורת"); }
          }
      
          async function sendTzintuk() {
              // אם אנחנו במצב INI, לא לבצע צינתוק בכלל
              if (currentMode === 'ini') return;
      
              const token = document.getElementById('token').value.trim();
              const rawPhones = document.getElementById('tz-phones').value.trim();
              const callerId = document.getElementById('tz-caller').value.trim() || 'RAND';
              const timeout = document.getElementById('tz-time').value;
      
              if (!rawPhones) return;
      
              const formattedPhones = rawPhones.split(',')
                  .map(p => p.trim())
                  .filter(p => p !== "")
                  .map(p => p.startsWith('tzl:') ? p : 'tzl:' + p)
                  .join(',');
      
              const url = `https://www.call2all.co.il/ym/api/RunTzintuk?token=${token}&phones=${formattedPhones}&callerId=${callerId}&intTzintukTimeOut=${timeout}`;
      
              try {
                  const response = await fetch(url);
                  const result = await response.text();
                  console.log("תגובת צינתוק: " + result);
              } catch (error) { console.error("שגיאת צינתוק:", error); }
          }
      
          async function executeUpload() {
              const token = document.getElementById('token').value;
              const folderInput = document.getElementById('folder').value.trim();
              if (!token || !folderInput) { alert("מלא פרטים"); return; }
              const folders = folderInput.split(',').map(f => f.trim()).filter(f => f !== "");
              const submitBtn = document.getElementById('mainSubmitBtn');
              const progBar = document.getElementById('progressBar');
              const progContainer = document.getElementById('progressContainer');
      
              submitBtn.disabled = true; progContainer.style.display = "block";
              let successCount = 0; let failCount = 0;
      
              for (let i = 0; i < folders.length; i++) {
                  const folder = folders[i];
                  submitBtn.innerText = `מעלה לשלוחה ${folder}... (${i+1}/${folders.length})`;
                  const formData = new FormData();
                  formData.append('token', token);
                  let apiUrl = "https://www.call2all.co.il/ym/api/UploadFile";
      
                  if (currentMode === 'file') {
                      const fIn = document.getElementById('fileInput');
                      if (!fIn.files.length) { alert("בחר קובץ"); break; }
                      formData.append('file', fIn.files[0]);
                      formData.append('convertAudio', '1');
                      let name = document.getElementById('fileNameAudio').value.trim();
                      let pathValue = "ivr2:" + folder + "/";
                      if (name) {
                          if (!name.toLowerCase().endsWith('.wav')) name += ".wav";
                          pathValue += name; formData.append('autoNumbering', 'false');
                      } else { formData.append('autoNumbering', 'true'); }
                      formData.append('path', pathValue);
                  } else {
                      apiUrl = "https://www.call2all.co.il/ym/api/UploadTextFile";
                      let name = currentMode === 'text' ? document.getElementById('fileNameText').value.trim() : document.getElementById('fileNameIni').value.trim();
                      let content = currentMode === 'text' ? document.getElementById('textContent').value : document.getElementById('iniContentNew').value;
                      if (currentMode === 'ini' && name && !name.toLowerCase().endsWith('.ini')) name += ".ini";
                      formData.append('what', `ivr2:${folder}/${name}`);
                      formData.append('contents', content);
                  }
      
                  try {
                      const res = await fetch(apiUrl, { method: "POST", body: formData });
                      const result = await res.json();
                      if (result.responseStatus === "OK") successCount++; else failCount++;
                  } catch (e) { failCount++; }
                  const percent = Math.round(((i + 1) / folders.length) * 100);
                  progBar.style.width = percent + "%"; progBar.innerText = percent + "%";
              }
      
              // ביצוע צינתוק בסיום - רק אם מסומן ורק אם לא במצב INI
              if (currentMode !== 'ini' && document.getElementById('use-tzintuk').checked) {
                  await sendTzintuk();
              }
      
              submitBtn.disabled = false; submitBtn.innerText = "בצע העלאה";
              alert(`הסתיים. הצלחות: ${successCount}, כשלונות: ${failCount}`);
              setTimeout(() => { progContainer.style.display = "none"; }, 5000);
          }
      </script>
      </body>
      </html>
      

      קרדיט: @אA


      קובץ להפעלת קמפיין - הנכנס לשלוחה במערכת

      הקוד מצורף בספויילר

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>שיגור קמפיין - הנכנס לשלוחה במערכת</title>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
          <style>
              :root {
                  --primary-color: #4a90e2;
                  --success-color: #27ae60;
                  --error-color: #e74c3c;
                  --border-radius: 12px;
              }
      
              body { 
                  font-family: 'Segoe UI', Arial, sans-serif; 
                  background-color: #f4f7f9;
                  margin: 0; padding: 20px; color: #333;
              }
      
              .container { 
                  max-width: 600px; margin: auto; background: white;
                  padding: 30px; border-radius: var(--border-radius); 
                  box-shadow: 0 4px 15px rgba(0,0,0,0.1); 
              }
      
              h2 { 
                  text-align: center; 
                  color: var(--primary-color);
                  display: flex; 
                  align-items: center;
                  justify-content: center;
                  gap: 10px; 
                  margin-bottom: 25px;
              }
      
              .speaker-icon {
                  width: 30px; height: 30px;
                  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%234a90e2"><path d="M14 3.23v17.54c0 .87-.98 1.34-1.6.84L7.37 15H2c-1.1 0-2-.9-2-2v-2c0-1.1.9-2 2-2h5.37L12.4 2.39c.62-.5 1.6-.03 1.6.84zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM19 12c0 2.97-1.87 5.51-4.5 6.74v-13.48c2.63 1.23 4.5 3.77 4.5 6.74z"/></svg>');
                  background-repeat: no-repeat; background-position: center; background-size: contain;
              }
      
              .form-group { margin-bottom: 15px; }
              label { display: block; font-weight: bold; margin-bottom: 5px; }
              input, textarea { 
                  width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px; box-sizing: border-box; 
              }
      
              .type-selector {
                  display: flex; gap: 15px; margin-bottom: 15px; background: #f9f9f9; padding: 10px; border-radius: 8px;
              }
              .type-selector label { font-weight: normal; cursor: pointer; display: flex; align-items: center; gap: 5px; margin: 0; }
      
              .ivr-input-wrapper { display: flex; direction: ltr; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
              .ivr-prefix { background: #eee; padding: 10px; border-right: 1px solid #ddd; font-weight: bold; }
              .ivr-input-wrapper input { border: none; flex: 1; outline: none; }
      
              button { 
                  background-color: var(--success-color); color: white; border: none; 
                  padding: 15px; cursor: pointer; border-radius: 8px; width: 100%; 
                  font-size: 18px; font-weight: bold; margin-top: 10px;
              }
      
              button:active { transform: scale(0.98); }
              button:disabled { background-color: #ccc; cursor: not-allowed; }
      
              #error-display { 
                  margin-top: 15px; padding: 15px; background: #fff5f5; 
                  border: 1px solid var(--error-color); border-radius: 8px; 
                  color: var(--error-color); display: none; font-size: 14px;
              }
      
              .modal {
                  display: none; position: fixed; z-index: 1000; left: 0; top: 0; 
                  width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(3px);
              }
      
              .modal-content {
                  background: white; margin: 15% auto; padding: 25px; 
                  border-radius: var(--border-radius); width: 90%; max-width: 450px;
                  text-align: center; box-shadow: 0 8px 25px rgba(0,0,0,0.2);
              }
      
              table { width: 100%; border-collapse: collapse; margin-top: 15px; direction: rtl; }
              th, td { border: 1px solid #ddd; padding: 10px; text-align: right; }
              th { background: #f9f9f9; }
              
              .close-btn { background: #666; color: white; padding: 8px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 15px; }
          </style>
      </head>
      <body>
      
      <div class="container">
          <h2>
              <div class="speaker-icon"></div>
              שיגור קמפיין - הנכנס לשלוחה
          </h2>
          
          <div class="form-group">
              <label>טוקן (Token):</label>
              <input type="text" id="token" placeholder="הכנס טוקן API">
          </div>
      
          <div class="form-group">
              <label>סוג יעד:</label>
              <div class="type-selector">
                  <label><input type="radio" name="targetType" value="manual" checked onchange="updateUI()"> רשימת מספרים</label>
                  <label><input type="radio" name="targetType" value="tpl" onchange="updateUI()"> רשימת תפוצה</label>
                  <label><input type="radio" name="targetType" value="tzl" onchange="updateUI()"> רשימת צינתוקים</label>
              </div>
          </div>
      
          <div class="form-group" id="excelGroup">
              <label>טעינת אקסל (אופציונלי):</label>
              <input type="file" id="excelFile" accept=".xlsx, .xls, .csv">
          </div>
      
          <div class="form-group">
              <label id="targetLabel">מספרי טלפון:</label>
              <textarea id="targetInput" rows="4" placeholder="מספר בכל שורה"></textarea>
          </div>
      
          <div class="form-group">
              <label>שלוחה במערכת:</label>
              <div class="ivr-input-wrapper">
                  <span class="ivr-prefix">ivr2:</span>
                  <input type="text" id="ivrPath" placeholder="/1/100">
              </div>
          </div>
      
          <div class="form-group">
              <label>זיהוי שיחה (Caller ID) - אופציונלי:</label>
              <input type="text" id="callerId" placeholder="ברירת מחדל">
          </div>
      
          <div class="form-group">
              <label>זמן המתנה (30-35 שניות):</label>
              <input type="number" id="callsTimeOut" value="30" min="30" max="35">
          </div>
      
          <button id="submitBtn" onclick="sendCampaign()">שיגור קמפיין</button>
          <div id="error-display"></div>
      </div>
      
      <div id="successModal" class="modal">
          <div class="modal-content">
              <h3 style="color: var(--success-color);">הקמפיין נוצר בהצלחה!</h3>
              <table id="resultTable"></table>
              <button class="close-btn" onclick="closeModal()">סגור</button>
          </div>
      </div>
      
      <script>
          function updateUI() {
              const type = document.querySelector('input[name="targetType"]:checked').value;
              const inputArea = document.getElementById('targetInput');
              const excelGroup = document.getElementById('excelGroup');
              const label = document.getElementById('targetLabel');
      
              inputArea.value = ''; // ניקוי התיבה במעבר בין סוגים
      
              if (type === 'manual') {
                  label.innerText = 'מספרי טלפון:';
                  inputArea.placeholder = 'מספר בכל שורה';
                  excelGroup.style.display = 'block';
              } else if (type === 'tpl') {
                  label.innerText = 'מזהה רשימת תפוצה:';
                  inputArea.placeholder = 'הכנס מספר מזהה בלבד';
                  excelGroup.style.display = 'none';
              } else {
                  label.innerText = 'מזהה רשימת צינתוקים:';
                  inputArea.placeholder = 'הכנס מספר מזהה בלבד';
                  excelGroup.style.display = 'none';
              }
          }
      
          document.getElementById('excelFile').addEventListener('change', function(e) {
              const file = e.target.files[0];
              const reader = new FileReader();
              reader.onload = function(event) {
                  try {
                      const data = new Uint8Array(event.target.result);
                      const workbook = XLSX.read(data, { type: 'array' });
                      const sheet = workbook.Sheets[workbook.SheetNames[0]];
                      const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 });
                      const phones = rows.map(r => r[0]).filter(c => c);
                      document.getElementById('targetInput').value = phones.join('\n');
                  } catch (err) { alert("שגיאה בקריאת הקובץ"); }
              };
              reader.readAsArrayBuffer(file);
          });
      
          async function sendCampaign() {
              const btn = document.getElementById('submitBtn');
              const errorDiv = document.getElementById('error-display');
              errorDiv.style.display = "none";
              btn.innerText = "שולח... נא להמתין";
              btn.disabled = true;
      
              const token = document.getElementById('token').value.trim();
              const userInput = document.getElementById('targetInput').value.trim();
              let ivrPath = document.getElementById('ivrPath').value.trim();
              const timeout = document.getElementById('callsTimeOut').value;
              const callerId = document.getElementById('callerId').value.trim();
              const targetType = document.querySelector('input[name="targetType"]:checked').value;
      
              if (!token || !userInput || !ivrPath) {
                  showError("חובה למלא את כל השדות");
                  resetBtn();
                  return;
              }
      
              // בניית פרמטר phones עם הקידומת המתאימה מאחורי הקלעים
              let phonesParam = "";
              if (targetType === 'tpl') {
                  phonesParam = `tpl:${userInput}`;
              } else if (targetType === 'tzl') {
                  phonesParam = `tzl:${userInput}`;
              } else {
                  phonesParam = userInput.split('\n').map(p => p.trim()).filter(p => p).join(':');
              }
      
              if (!ivrPath.startsWith('/')) ivrPath = '/' + ivrPath;
              const finalIvrPath = `ivr2:${ivrPath}`;
      
              let url = `https://www.call2all.co.il/ym/api/CallExtensionBridging?token=${token}&phones=${phonesParam}&ivrPath=${finalIvrPath}&callsTimeOut=${timeout}`;
              if (callerId) url += `&callerId=${callerId}`;
      
              try {
                  const response = await fetch(url);
                  const result = await response.json();
                  if (result.responseStatus === "OK") {
                      showSuccess(result);
                  } else {
                      showError("שגיאת מערכת: " + (result.message || "שגיאה"));
                  }
              } catch (e) {
                  showError("שגיאת תקשורת.");
              } finally {
                  resetBtn();
              }
          }
      
          function showSuccess(data) {
              const table = document.getElementById('resultTable');
              table.innerHTML = `<tr><td>סטטוס</td><td>${data.responseStatus}</td></tr>
                                 <tr><td>מזהה קמפיין</td><td>${data.campaignId || 'אין'}</td></tr>
                                 <tr><td>מספרים שנקלטו</td><td>${data.callsCount || '0'}</td></tr>`;
              document.getElementById('successModal').style.display = "block";
          }
      
          function showError(msg) {
              const errorDiv = document.getElementById('error-display');
              errorDiv.innerText = msg;
              errorDiv.style.display = "block";
          }
      
          function resetBtn() {
              const btn = document.getElementById('submitBtn');
              btn.innerText = "שיגור קמפיין";
              btn.disabled = false;
          }
      
          function closeModal() { document.getElementById('successModal').style.display = "none"; }
      </script>
      
      </body>
      </html>
      

      קרדיט: @אA


      ממשק ניהול הקבצים במערכת

      ממשק ניהול קבצים במערכת.html

      הקוד מצורף בספויילר

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
      <meta charset="UTF-8">
      <title>ניהול שלוחות ימות המשיח</title>
      <style>
      :root {
      --primary: #2c3e50; --accent: #3498db; --success: #27ae60;
      --danger: #e74c3c; --violet: #8e44ad; --orange: #f39c12;
      }
      body { font-family: 'Segoe UI', sans-serif; background: #f4f7f6; margin: 0; padding: 20px; }

          /* סרגל עליון */
          .top-bar { 
              background: white; padding: 15px; border-radius: 10px; 
              box-shadow: 0 2px 10px rgba(0,0,0,0.05); display: flex; 
              flex-direction: column; gap: 10px; margin-bottom: 20px; 
          }
          .inputs-row { display: flex; gap: 10px; align-items: center; width: 100%; }
          input { padding: 8px; border: 1px solid #ddd; border-radius: 5px; flex: 1; }
      
          /* עיצוב רכיב הטוקנים */
          .token-main { position: relative; flex: 1; display: flex; } /* גודל זהה לשדה הנתיב */
          #tokenField { flex: 1; width: 100%; }
          
          .token-dropdown { 
              position: absolute; width: 100%; background: white; border: 1px solid #ccc; 
              border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); 
              z-index: 1000; max-height: 250px; overflow-y: auto; display: none; top: 100%;
          }
          .token-item { 
              display: flex; justify-content: space-between; align-items: center; 
              padding: 10px; cursor: pointer; border-bottom: 1px solid #eee; color: #333;
          }
          .token-item:hover { background: #f8f9fa; }
          .delete-item { color: #dc3545; font-weight: bold; padding: 5px 10px; cursor: pointer; border: none; background: none; }
          .dropdown-footer { padding: 8px; background: #f1f3f5; display: flex; gap: 5px; justify-content: center; border-top: 1px solid #ddd; }
          .footer-btn { font-size: 11px; padding: 4px 8px; cursor: pointer; border: 1px solid #ccc; background: white; border-radius: 4px; }
          
          /* כפתורים וטבלה */
          .btn { cursor: pointer; border: none; border-radius: 4px; padding: 4px 8px; font-weight: bold; color: white; transition: 0.2s; font-size: 11px; text-align: center; display: inline-block; width: 100%; max-width: 75px; }
          .btn:hover { opacity: 0.85; transform: translateY(-1px); }
          .btn-load { background: var(--primary); padding: 8px 18px; width: auto; max-width: none; font-size: 14px; }
          
          .ext-container { background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); margin-bottom: 20px; border-top: 4px solid var(--primary); }
          .ext-container h4 { margin: 0 0 10px 0; color: var(--primary); display: flex; justify-content: space-between; align-items: center; }
          #extTextArea { width: 100%; height: 120px; font-family: 'Consolas', monospace; font-size: 13px; padding: 10px; box-sizing: border-box; border: 1px solid #ddd; background: #fcfcfc; border-radius: 5px; }
      
          table { width: 100%; background: white; border-collapse: collapse; border-radius: 8px; overflow: hidden; }
          th { background: #f8f9fa; padding: 10px; font-size: 12px; border-bottom: 2px solid #eee; }
          td { padding: 5px 8px; border-bottom: 1px solid #eee; text-align: center; font-size: 12px; }
          
          .dl { background: var(--success); } .ed { background: var(--orange); } .up { background: var(--accent); } .del { background: var(--danger); } .view { background: var(--violet); }
      
          .modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.6); justify-content: center; align-items: center; z-index: 1000; }
          .modal-content { background: white; padding: 25px; border-radius: 12px; width: 600px; max-width: 95%; }
          .modal-footer { margin-top: 20px; display: flex; justify-content: flex-start; flex-direction: row-reverse; gap: 10px; }
          .convert-row { display: flex; gap: 10px; justify-content: center; margin-top: 20px; }
      </style>
      

      </head>
      <body>

      <div class="top-bar">
      <div class="inputs-row">
      <div class="token-main">
      <input type="text" id="tokenField" placeholder="הזן טוקן או בחר"
      onclick="toggleDropdown(true)" oninput="filterDropdown(this.value)" onkeypress="handleEnter(event)" autocomplete="off">
      <div id="tokenDropdown" class="token-dropdown"></div>
      </div>

          <input type="text" id="pathField" value="" placeholder="נתיב שלוחה" onkeypress="handleEnter(event)">
          <button class="btn btn-load" onclick="startProcess()">טען רשימת קבצים</button>
      </div>
      
      <div style="display: flex; align-items: center; gap: 15px; padding-top: 5px;">
          <div style="display: flex; align-items: center; gap: 5px;">
              <input type="checkbox" id="saveTokenToggle" style="width: auto; cursor: pointer;" 
                     onchange="document.getElementById('tokenNameField').style.display = this.checked ? 'block' : 'none'">
              <label for="saveTokenToggle" style="font-size: 12px; cursor: pointer; color: #666;">שמור טוקן זה במאגר</label>
          </div>
          <div id="tokenNameField" style="display:none;">
              <input type="text" id="tokenAlias" placeholder="שם למזהה (למשל: המערכת שלי)" style="font-size: 12px; padding: 5px; width: 200px;">
          </div>
      </div>
      

      </div>

      <div class="ext-container" id="extContainer" style="display:none;">
      <h4>
      <span>הגדרות שלוחה (ext.ini)</span>
      <div style="display:flex; gap:8px;">
      <button class="btn" style="background:var(--orange); width:auto; padding:5px 15px;" onclick="renameExtIni()">שנה שם</button>
      <button class="btn" style="background:var(--success); width:auto; padding:5px 15px;" onclick="saveExtIni()">שמור שינויים</button>
      </div>
      </h4>
      <textarea id="extTextArea"></textarea>
      </div>

      <table>
      <thead>
      <tr>
      <th style="text-align: right; width: 25%;">שם קובץ</th>
      <th>גודל</th>
      <th>תצוגה</th>
      <th>הורדה</th>
      <th>שינוי שם</th>
      <th>החלפה</th>
      <th>מחיקה</th>
      </tr>
      </thead>
      <tbody id="tableBody"></tbody>
      </table>

      <div id="mainModal" class="modal">
      <div class="modal-content">
      <h3 id="modalTitle" style="margin:0 0 15px 0; border-bottom:1px solid #eee; padding-bottom:10px;"></h3>
      <div id="modalBody"></div>
      <div class="modal-footer" id="modalFooter">
      <button id="saveBtn" class="btn btn-load">שמור</button>
      <button class="btn" onclick="closeModal()" style="background:#95a5a6;">ביטול</button>
      </div>
      </div>
      </div>

      <script>
      const API_BASE = 'https://www.call2all.co.il/ym/api/';
      const STORAGE_KEY = 'yemot_permanent_storage';
      let savedTokens = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');

      document.addEventListener('click', (e) => { if (!e.target.closest('.token-main')) toggleDropdown(false); });
      function toggleDropdown(show) {
          const dropdown = document.getElementById('tokenDropdown');
          if (show) { renderDropdown(); dropdown.style.display = 'block'; }
          else { dropdown.style.display = 'none'; }
      }
      function renderDropdown(filter = "") {
          const dropdown = document.getElementById('tokenDropdown');
          dropdown.innerHTML = '';
          const filteredKeys = Object.keys(savedTokens).filter(a => a.toLowerCase().includes(filter.toLowerCase()));
          filteredKeys.forEach(alias => {
              const item = document.createElement('div');
              item.className = 'token-item';
              item.innerHTML = `<span>${alias}</span><button type="button" class="delete-item">✖</button>`;
              item.onclick = () => { document.getElementById('tokenField').value = savedTokens[alias]; toggleDropdown(false); };
              item.querySelector('.delete-item').onclick = (e) => {
                  e.stopPropagation();
                  if(confirm(`למחוק את ${alias}?`)) { 
                      delete savedTokens[alias]; 
                      localStorage.setItem(STORAGE_KEY, JSON.stringify(savedTokens)); 
                      renderDropdown(filter); 
                  }
              };
              dropdown.appendChild(item);
          });
          const footer = document.createElement('div');
          footer.className = 'dropdown-footer';
          footer.innerHTML = `<button type="button" class="footer-btn" style="color:red" onclick="if(confirm('למחוק הכל?')){savedTokens={};localStorage.setItem(STORAGE_KEY,'{}');renderDropdown();}">מחק הכל</button>`;
          footer.onclick = (e) => e.stopPropagation();
          dropdown.appendChild(footer);
      }
      function filterDropdown(val) { renderDropdown(val); document.getElementById('tokenDropdown').style.display = 'block'; }
      
      function startProcess() {
          saveCurrentTokenIfRequested();
          loadDirectory();
      }
      
      function saveCurrentTokenIfRequested() {
          const token = document.getElementById('tokenField').value.trim();
          const alias = document.getElementById('tokenAlias').value.trim();
          if (document.getElementById('saveTokenToggle').checked && alias && token) {
              savedTokens[alias] = token;
              localStorage.setItem(STORAGE_KEY, JSON.stringify(savedTokens));
          }
      }
      
      function handleEnter(event) { if (event.key === "Enter") startProcess(); }
      
      function getFullPath(fileName = "") {
          let rawPath = document.getElementById('pathField').value.trim();
          if (rawPath && !rawPath.endsWith('/') && fileName !== "") rawPath += '/';
          return `ivr2:${rawPath}${fileName}`;
      }
      
      async function callApi(method, params = {}) {
          const token = document.getElementById('tokenField').value.trim();
          if (!token) return;
          let url = `${API_BASE}${method}?token=${token}`;
          for (let key in params) url += `&${key}=${encodeURIComponent(params[key])}`;
          const res = await fetch(url);
          return await res.json();
      }
      
      async function loadDirectory() {
          const data = await callApi('GetIVR2Dir', { path: getFullPath() });
          const body = document.getElementById('tableBody');
          body.innerHTML = '';
          if (data && data.files) {
              data.files.forEach(file => {
                  const isText = file.name.endsWith('.ini') || file.name.endsWith('.tts');
                  body.innerHTML += `
                      <tr>
                          <td style="text-align: right;"><strong>${file.name}</strong></td>
                          <td style="color:#888;">${file.size} B</td>
                          <td>${isText ? `<button class="btn view" onclick="openTextFile('${file.name}')">הצג</button>` : '-'}</td>
                          <td><button class="btn dl" onclick="handleDownloadClick('${file.name}')">הורדה</button></td>
                          <td><button class="btn ed" onclick="renameFile('${file.name}')">שינוי</button></td>
                          <td><button class="btn up" onclick="uploadUI('${file.name}')">החלפה</button></td>
                          <td><button class="btn del" onclick="deleteFile('${file.name}')">מחיקה</button></td>
                      </tr>`;
              });
              loadExtIni();
          }
      }
      
      async function loadExtIni() {
          const data = await callApi('GetTextFile', { what: getFullPath('ext.ini') });
          document.getElementById('extContainer').style.display = 'block';
          document.getElementById('extTextArea').value = (data && data.exists !== false) ? data.contents || "" : "קובץ ext.ini לא נמצא.";
      }
      
      async function saveExtIni() {
          const res = await callApi('UploadTextFile', { what: getFullPath('ext.ini'), contents: document.getElementById('extTextArea').value });
          if (res && res.responseStatus === "OK") alert("נשמר!");
      }
      
      async function renameExtIni() {
          const newName = prompt("שם חדש ל-ext.ini:", "ext.ini");
          if (newName) {
              const res = await callApi('FileAction', { action: 'move', what: getFullPath('ext.ini'), target: getFullPath(newName) });
              if (res && res.responseStatus === "OK") loadDirectory();
          }
      }
      
      function handleDownloadClick(name) {
          if (name.toLowerCase().endsWith('.ymgr')) showConvertModal(name);
          else window.open(`${API_BASE}DownloadFile?token=${document.getElementById('tokenField').value}&path=${getFullPath(name)}`);
      }
      
      function showConvertModal(name) {
          const html = `
              <p style="text-align:center;">בחר פורמט המרה עבור קובץ הנתונים:</p>
              <div class="convert-row">
                  <button class="btn" style="background:var(--primary); max-width:none; flex:1; padding:10px;" onclick="executeRender('${name}', '')">ללא המרה</button>
                  <button class="btn" style="background:var(--success); max-width:none; flex:1; padding:10px;" onclick="executeRender('${name}', 'csv')">אקסל (CSV)</button>
                  <button class="btn" style="background:var(--violet); max-width:none; flex:1; padding:10px;" onclick="executeRender('${name}', 'html')">דף HTML</button>
              </div>`;
          showModal("המרה והורדה", html);
          document.getElementById('modalFooter').style.display = 'none';
      }
      
      function executeRender(name, type) {
          const token = document.getElementById('tokenField').value;
          const url = type === "" ? 
              `${API_BASE}DownloadFile?token=${token}&path=${getFullPath(name)}` : 
              `${API_BASE}RenderYMGRFile?token=${token}&wath=${getFullPath(name)}&convertType=${type}`;
          window.open(url);
          closeModal();
      }
      
      function showModal(title, html) {
          document.getElementById('modalTitle').innerText = title;
          document.getElementById('modalBody').innerHTML = html;
          document.getElementById('mainModal').style.display = 'flex';
          document.getElementById('modalFooter').style.display = 'flex';
      }
      function closeModal() { document.getElementById('mainModal').style.display = 'none'; }
      
      async function deleteFile(name) { if (confirm(`למחוק את ${name}?`)) { const res = await callApi('FileAction', { action: 'delete', what: getFullPath(name) }); if (res.responseStatus === "OK") loadDirectory(); } }
      function renameFile(name) { const newName = prompt("שם חדש:", name); if (newName) callApi('FileAction', { action: 'move', what: getFullPath(name), target: getFullPath(newName) }).then(loadDirectory); }
      async function openTextFile(name) {
          const data = await callApi('GetTextFile', { what: getFullPath(name) });
          showModal(`עריכת ${name}`, `<textarea id="modalTextArea" style="width:100%; height:300px; font-family:monospace;">${data.contents || ''}</textarea>`);
          document.getElementById('saveBtn').onclick = async () => { await callApi('UploadTextFile', { what: getFullPath(name), contents: document.getElementById('modalTextArea').value }); closeModal(); loadDirectory(); };
      }
      function uploadUI(name) {
          showModal(`החלפת ${name}`, `<input type="file" id="fInp" style="margin-top:10px;">`);
          document.getElementById('saveBtn').onclick = async () => {
              const token = document.getElementById('tokenField').value;
              const file = document.getElementById('fInp').files[0];
              if (!file) return;
              const fd = new FormData(); fd.append('file', file);
              await fetch(`${API_BASE}UploadFile?token=${token}&path=${getFullPath()}`, { method: 'POST', body: fd });
              closeModal(); loadDirectory();
          };
      }
      

      </script>
      </body>
      </html>

      קרדיט: @אA


      ממשק ליצירת ניהול והפעלת קמפיין

      לא בדקתי את הפעולה שלו בזמן אמת, אשמח אם יש מי שהשתמש בזה שיעדכן.

      הקוד מצורף בספויילר

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
      <meta charset="UTF-8">
      <title>מערכת ניהול קמפיינים - Yemot API</title>
      <style>
      :root {
      --primary-color: #2c3e50;
      --accent-color: #3498db;
      --bg-color: #f8f9fa;
      }
      body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: var(--bg-color); margin: 0; padding: 20px; }
      .container { max-width: 1100px; margin: auto; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); overflow: hidden; }

          /* תפריט עליון */
          .tabs { display: flex; background: var(--primary-color); padding: 0 10px; }
          .tab-button { 
              padding: 15px 25px; border: none; background: none; color: white; 
              cursor: pointer; font-size: 16px; transition: 0.3s; border-bottom: 3px solid transparent;
          }
          .tab-button:hover { background: rgba(255,255,255,0.1); }
          .tab-button.active { border-bottom: 3px solid var(--accent-color); font-weight: bold; }
      
          /* תוכן החלונות */
          .tab-content { display: none; padding: 30px; animation: fadeIn 0.4s; }
          .tab-content.active { display: block; }
      
          @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
      
          .form-group { margin-bottom: 20px; }
          label { display: block; margin-bottom: 8px; font-weight: 600; color: #444; }
          input, select, textarea { 
              width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; 
              font-size: 14px; box-sizing: border-box;
          }
          .btn-row { display: flex; gap: 10px; margin-top: 20px; }
          button { 
              padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; 
              font-weight: bold; transition: 0.2s;
          }
          .btn-primary { background: var(--accent-color); color: white; }
          .btn-danger { background: #e74c3c; color: white; }
          .btn-success { background: #27ae60; color: white; }
          
          .response-area { 
              margin-top: 25px; padding: 15px; background: #272c33; color: #61dafb; 
              border-radius: 6px; font-family: monospace; font-size: 13px; white-space: pre-wrap;
              max-height: 250px; overflow-y: auto; direction: ltr; text-align: left;
          }
          .info-box { background: #e8f4fd; padding: 15px; border-radius: 6px; border-right: 4px solid var(--accent-color); margin-bottom: 20px; }
      </style>
      

      </head>
      <body>

      <div class="container">
      <div style="background: #eee; padding: 15px 30px; border-bottom: 1px solid #ddd; display: flex; align-items: center; gap: 15px;">
      <label style="margin:0;">טוקן גישה (token):</label>
      <input type="text" id="mainToken" placeholder="הכנס טוקן API כאן" style="width: 350px;">
      </div>

      <nav class="tabs">
          <button class="tab-button active" onclick="openTab(event, 'setup')">הקמה והגדרות</button>
          <button class="tab-button" onclick="openTab(event, 'run')">הפעלת קמפיין</button>
          <button class="tab-button" onclick="openTab(event, 'contacts')">ניהול מספרים</button>
          <button class="tab-button" onclick="openTab(event, 'monitor')">בקרה בזמן אמת</button>
          <button class="tab-button" onclick="openTab(event, 'schedule')">תזמון</button>
      </nav>
      
      <div id="setup" class="tab-content active">
          <h2>הקמת תבנית קמפיין חדשה</h2>
          <div class="form-group">
              <label>שם הקמפיין (תיאור):</label>
              <input type="text" id="setup_desc" placeholder="לדוגמה: קמפיין התרמה">
          </div>
          <button class="btn-primary" onclick="executeCall('CreateTemplate', {description: 'setup_desc'})">צור תבנית חדשה</button>
          <div class="response-area" id="res-setup">// ממתין לפעולה...</div>
      </div>
      
      <div id="run" class="tab-content">
          <h2>הפעלת קמפיין</h2>
          <div class="form-group">
              <label>מזהה תבנית:</label>
              <input type="number" id="run_tpl">
          </div>
          <div class="form-group">
              <label>רשימת מספרים - הפרדה בנקודותיים:</label>
              <textarea id="run_phones" rows="3" placeholder="0501234567:0507654321"></textarea>
          </div>
          <button class="btn-success" onclick="executeCall('RunCampaign', {templateId: 'run_tpl', phones: 'run_phones'})">שגר קמפיין עכשיו</button>
          <div class="response-area" id="res-run">// ממתין לפעולה...</div>
      </div>
      
      <div id="contacts" class="tab-content">
          <h2>ניהול רשימות תפוצה</h2>
          <div class="form-group">
              <label>מזהה תבנית:</label>
              <input type="number" id="list_tpl">
          </div>
          <div class="form-group">
              <label>נתוני טלפונים:</label>
              <textarea id="list_data" rows="5" placeholder="0501112233,ישראל ישראלי"></textarea>
          </div>
          <div class="btn-row">
              <button class="btn-primary" onclick="executeCall('UploadPhoneList', {templateId: 'list_tpl', data: 'list_data'})">העלה רשימה</button>
              <button class="btn-danger" onclick="executeCall('ClearTemplateEntries', {templateId: 'list_tpl'})">מחק את כל הרשימה</button>
          </div>
          <div class="response-area" id="res-contacts">// ממתין לפעולה...</div>
      </div>
      
      <div id="monitor" class="tab-content">
          <h2>ניהול קמפיין פעיל</h2>
          <div class="form-group">
              <label>מזהה קמפיין רץ:</label>
              <input type="text" id="active_id">
          </div>
          <div class="btn-row">
              <button class="btn-primary" onclick="executeCall('GetCampaignStatus', {campaignId: 'active_id'})">בדוק סטטוס</button>
              <button class="btn-danger" onclick="executeCall('CampaignAction', {campaignId: 'active_id', action: 'stop'})">עצור קמפיין</button>
              <button style="background: #f39c12; color:white;" onclick="executeCall('CampaignAction', {campaignId: 'active_id', action: 'setPaused'})">השהה או המשך</button>
          </div>
          <div class="response-area" id="res-monitor">// ממתין לפעולה...</div>
      </div>
      
      <div id="schedule" class="tab-content">
          <h2>תזמון קמפיין עתידי</h2>
          <div class="form-group">
              <label>מזהה תבנית:</label>
              <input type="number" id="sched_tpl">
          </div>
          <div class="form-group">
              <label>זמן לביצוע:</label>
              <input type="text" id="sched_time" placeholder="yyyy-MM-dd HH:mm:ss">
          </div>
          <button class="btn-primary" onclick="executeCall('ScheduleCampaign', {templateId: 'sched_tpl', time: 'sched_time'})">קבע תזמון</button>
          <div class="response-area" id="res-schedule">// ממתין לפעולה...</div>
      </div>
      

      </div>

      <script>
      const API_BASE = "https://www.call2all.co.il/ym/api/";

      function openTab(evt, tabName) {
          var i, tabContent, tabButtons;
          tabContent = document.getElementsByClassName("tab-content");
          for (i = 0; i < tabContent.length; i++) tabContent[i].classList.remove("active");
          tabButtons = document.getElementsByClassName("tab-button");
          for (i = 0; i < tabButtons.length; i++) tabButtons[i].classList.remove("active");
          document.getElementById(tabName).classList.add("active");
          evt.currentTarget.classList.add("active");
      }
      
      function executeCall(command, params) {
          const token = document.getElementById('mainToken').value;
          if(!token) { alert("חובה להזין טוקן!"); return; }
      
          let url = new URL(API_BASE + command);
          url.searchParams.append("token", token);
      
          for (let key in params) {
              let val = params[key];
              let element = document.getElementById(val);
              if(element) {
                  url.searchParams.append(key, element.value);
              } else {
                  url.searchParams.append(key, val);
              }
          }
      
          const activeResArea = document.querySelector('.tab-content.active .response-area');
          activeResArea.innerHTML = "שולח בקשה...\n\nURL:\n" + url.href;
      }
      

      </script>

      </body>
      </html>

      קרדיט: @אa


      הוספת טקסט למספר קבצים במערכת

      הקוד מצורף בספויילר

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>ניהול קבצים - ימות המשיח</title>
          <style>
              :root {
                  --primary-color: #4a90e2;
                  --secondary-color: #2ecc71;
                  --danger-color: #e74c3c;
                  --bg-color: #f4f7f6;
                  --text-color: #333;
                  --card-bg: #ffffff;
                  --border-radius: 12px;
              }
      
              body {
                  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                  background-color: var(--bg-color);
                  color: var(--text-color);
                  margin: 0;
                  padding: 20px;
                  display: flex;
                  justify-content: center;
              }
      
              .container {
                  background-color: var(--card-bg);
                  padding: 30px;
                  border-radius: var(--border-radius);
                  box-shadow: 0 10px 25px rgba(0,0,0,0.1);
                  max-width: 650px;
                  width: 100%;
              }
      
              h2 {
                  text-align: center;
                  color: var(--primary-color);
                  margin-bottom: 30px;
                  font-weight: 600;
              }
      
              .input-group {
                  margin-bottom: 20px;
              }
      
              label {
                  display: block;
                  margin-bottom: 8px;
                  font-weight: bold;
                  font-size: 0.9em;
              }
      
              input[type="text"], textarea {
                  width: 100%;
                  padding: 12px;
                  border: 2px solid #eee;
                  border-radius: 8px;
                  box-sizing: border-box;
                  transition: border-color 0.3s;
                  font-size: 16px;
              }
      
              input[type="text"]:focus, textarea:focus {
                  border-color: var(--primary-color);
                  outline: none;
              }
      
              .flex-row {
                  display: flex;
                  gap: 10px;
                  align-items: center;
              }
      
              button {
                  padding: 12px 20px;
                  border: none;
                  border-radius: 8px;
                  cursor: pointer;
                  font-weight: bold;
                  transition: all 0.3s;
                  display: inline-flex;
                  align-items: center;
                  justify-content: center;
                  white-space: nowrap;
              }
      
              .btn-load {
                  background-color: var(--primary-color);
                  color: white;
                  min-width: 120px;
              }
      
              .btn-load:hover { background-color: #357abd; transform: translateY(-1px); }
      
              .btn-add {
                  background-color: var(--secondary-color);
                  color: white;
                  font-size: 0.9em;
                  gap: 8px;
              }
      
              .btn-add:hover { background-color: #27ae60; }
      
              .btn-delete {
                  background-color: var(--danger-color);
                  color: white;
                  padding: 10px;
                  min-width: 40px;
              }
      
              .btn-delete:hover { background-color: #c0392b; }
      
              .file-input-wrapper {
                  display: flex;
                  gap: 8px;
                  margin-bottom: 10px;
                  animation: fadeIn 0.3s ease;
              }
      
              @keyframes fadeIn {
                  from { opacity: 0; transform: translateY(10px); }
                  to { opacity: 1; transform: translateY(0); }
              }
      
              textarea {
                  height: 180px;
                  resize: vertical;
                  background-color: #fafafa;
                  font-family: monospace;
                  margin-top: 10px;
              }
      
              /* עיצוב חדש לאזור העלאת קובץ מקומי */
              .file-upload-zone {
                  margin: 15px 0;
                  padding: 15px;
                  border: 2px dashed #ccc;
                  border-radius: 8px;
                  text-align: center;
                  background-color: #f9f9f9;
              }
      
              .custom-file-upload {
                  display: inline-block;
                  padding: 8px 16px;
                  cursor: pointer;
                  background-color: #fff;
                  border: 1px solid var(--primary-color);
                  color: var(--primary-color);
                  border-radius: 4px;
                  font-weight: bold;
                  transition: 0.3s;
              }
      
              .custom-file-upload:hover {
                  background-color: var(--primary-color);
                  color: white;
              }
      
              #localFileInput {
                  display: none;
              }
      
              .btn-upload {
                  background-color: #e67e22;
                  color: white;
                  width: 100%;
                  font-size: 1.1em;
                  margin-top: 10px;
                  box-shadow: 0 4px 15px rgba(230, 126, 34, 0.3);
              }
      
              .btn-upload:hover { background-color: #d35400; }
          </style>
      </head>
      <body>
      
      <div class="container">
          <h2>ניהול קבצי מערכת</h2>
      
          <div class="input-group">
              <label>טוקן אישי</label>
              <input type="text" id="token" placeholder="הכנס את ה-Token שלך">
          </div>
      
          <div class="input-group">
              <label>מספר שלוחה</label>
              <div class="flex-row">
                  <input type="text" id="ext" placeholder="למשל: 1">
                  <button class="btn-load" onclick="loadFirstFile()">טען תוכן מהשרת</button>
              </div>
          </div>
      
          <div class="input-group">
              <label>שמות קבצים ליעד</label>
              <div id="file-fields">
                  <div class="file-input-wrapper">
                      <input type="text" class="filename-input" placeholder="שם קובץ (למשל ext.ini)">
                      <button class="btn-add" onclick="addNewFileField()">קובץ נוסף +</button>
                  </div>
              </div>
          </div>
      
          <div class="input-group">
              <label>תוכן הקובץ</label>
              <textarea id="fileContents" placeholder="כאן יופיע תוכן הקובץ..."></textarea>
              
              <div class="file-upload-zone">
                  <label for="localFileInput" class="custom-file-upload">
                      📂 טען טקסט מקובץ במחשב
                  </label>
                  <input type="file" id="localFileInput" accept=".txt,.ini,.csv,.text" onchange="readLocalFile(this)">
                  <div id="fileNameDisplay" style="margin-top: 8px; font-size: 0.8em; color: #666;"></div>
              </div>
          </div>
      
          <button class="btn-upload" onclick="uploadAll()">העלה קבצים למערכת</button>
      </div>
      
      <script>
          const baseUrl = 'https://www.call2all.co.il/ym/api/';
      
          // פונקציה לקריאת קובץ מקומי מהמחשב
          function readLocalFile(input) {
              const file = input.files[0];
              if (!file) return;
      
              const reader = new FileReader();
              document.getElementById('fileNameDisplay').innerText = "קובץ שנבחר: " + file.name;
      
              reader.onload = function(e) {
                  document.getElementById('fileContents').value = e.target.result;
              };
      
              reader.readAsText(file);
          }
      
          function addNewFileField() {
              const container = document.getElementById('file-fields');
              const newDiv = document.createElement('div');
              newDiv.className = 'file-input-wrapper';
              newDiv.innerHTML = `
                  <input type="text" class="filename-input" placeholder="שם קובץ נוסף">
                  <button class="btn-delete" onclick="removeField(this)" title="מחק שדה">🗑️</button>
              `;
              container.appendChild(newDiv);
          }
      
          function removeField(btn) {
              btn.parentElement.remove();
          }
      
          async function loadFirstFile() {
              const token = document.getElementById('token').value;
              const ext = document.getElementById('ext').value;
              const firstInput = document.querySelector('.filename-input');
              const fileName = firstInput ? firstInput.value : '';
      
              if (!token || !fileName) {
                  alert("נא למלא טוקן ושם קובץ בתיבה הראשונה");
                  return;
              }
      
              const path = `ivr2:${ext}/${fileName}`;
              try {
                  const response = await fetch(`${baseUrl}GetTextFile?token=${token}&what=${path}`);
                  const data = await response.json();
                  if (data.responseStatus === "OK") {
                      document.getElementById('fileContents').value = data.contents || "הקובץ ריק";
                  } else {
                      alert("שגיאה: " + (data.message || "לא ניתן לטעון קובץ"));
                  }
              } catch (error) { alert("שגיאת תקשורת"); }
          }
      
          async function uploadAll() {
              const token = document.getElementById('token').value;
              const ext = document.getElementById('ext').value;
              const content = document.getElementById('fileContents').value;
              const fileInputs = document.querySelectorAll('.filename-input');
      
              if (!token) { alert("אנא הזן טוקן"); return; }
      
              let successCount = 0;
              let fileCount = 0;
      
              for (let input of fileInputs) {
                  const fileName = input.value;
                  if (!fileName) continue;
                  
                  fileCount++;
                  const path = `ivr2:${ext}/${fileName}`;
                  const url = `${baseUrl}UploadTextFile?token=${token}&what=${path}&contents=${encodeURIComponent(content)}`;
                  
                  try {
                      const res = await fetch(url);
                      const data = await res.json();
                      if (data.responseStatus === "OK") successCount++;
                  } catch (e) { console.error(e); }
              }
      
              if (fileCount === 0) {
                  alert("לא הוזנו שמות קבצים ליעד");
              } else {
                  alert(`הסתיים! ${successCount} מתוך ${fileCount} קבצים עודכנו בהצלחה.`);
              }
          }
      </script>
      
      </body>
      </html>
      

      קרדיט: @אA


      ממשק להתקנת תא קולי מתקדם על המערכת שלכם בהכנסת מספר וסיסמא בלבד!

      הקוד הוא (מדריך להפיכת הקוד לקובץ בפוסט הראשון)

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מתקין מערכת - גרסה מתוקנת</title>
          <style>
              body { font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f7f9; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; padding: 20px; flex-direction: column; }
              .card { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); width: 500px; text-align: center; }
              h2 { color: #2c3e50; margin-bottom: 20px; }
              input[type="text"], input[type="password"] { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid #dcdfe6; border-radius: 8px; box-sizing: border-box; font-size: 16px; }
              .folder-input { background: #f9f9f9; padding: 20px; border: 2px dashed #409eff; border-radius: 8px; margin: 20px 0; cursor: pointer; transition: 0.3s; }
              .folder-input:hover { background: #ecf5ff; }
              button { background-color: #67c23a; color: white; border: none; padding: 15px; border-radius: 8px; cursor: pointer; width: 100%; font-size: 16px; font-weight: bold; transition: 0.3s; }
              button:hover { background-color: #85ce61; }
              #install-box { display: none; }
              .progress-container { background: #ebeef5; border-radius: 10px; height: 25px; width: 100%; margin: 20px 0; overflow: hidden; border: 1px solid #eee; }
              .progress-fill { background: linear-gradient(90deg, #409eff, #66b1ff); width: 0%; height: 100%; transition: width 0.3s ease; }
              #status { font-size: 15px; color: #2980b9; font-weight: bold; margin-top: 10px; min-height: 1.5em; }
              .success-text { color: #67c23a; font-weight: bold; font-size: 20px; margin-top: 15px; }
              .next-steps { text-align: right; background: #fffdf0; border-right: 5px solid #f1c40f; padding: 15px; margin-top: 20px; font-size: 14px; line-height: 1.6; color: #333; display: none; }
              .next-steps h4 { margin-top: 0; color: #d35400; }
              .credit { font-size: 11px; color: #bdc3c7; margin-top: 20px; }
          </style>
      </head>
      <body>
       
      <div class="card" id="login-box">
          <h2>התקנת מערכת תא קולי</h2>
          <input type="text" id="sysId" placeholder="מספר מערכת">
          <input type="password" id="pass" placeholder="סיסמה">
          
          <div class="folder-input" onclick="document.getElementById('folder-select').click()">
              <strong>לחץ כאן לבחירת תיקיית הגיבוי</strong>
              <input type="file" id="folder-select" webkitdirectory directory multiple style="display:none">
              <div id="file-count" style="margin-top: 10px; font-size: 13px; color: #909399;">טרם נבחרה תיקייה</div>
          </div>
       
          <button onclick="runInstaller()">בצע התקנה ושחזור קבצים</button>
      </div>
       
      <div class="card" id="install-box">
          <h2 id="title-main">ההתקנה בביצוע...</h2>
          <div class="progress-container">
              <div id="fill" class="progress-fill"></div>
          </div>
          <div id="status">מתחבר למערכת...</div>
          
          <div id="final-msg" class="success-text"></div>
          <div id="sub-msg" style="font-weight: bold; color: #2c3e50; margin-top: 10px; display: none;"></div>
       
          <div id="instructions" class="next-steps">
              <h4>כמה פעולות שיש עוד לעשות:</h4>
              1. הרשמה לקבלת צינתוקים בשלוחה 6 בשלוחת ניהול התא.<br>
              2. נבדוק את הקמפיינים במערכת (בלשונית 'שיגור הודעות' באתר הישן).<br>
              <strong>ההגדרות צריכות להיות כמו בברירת מחדל:</strong><br>
              • שיש רק קמפיין אחד.<br>
              • בלשונית 'הגדרות קמפיין', באופציות של 'הגדרות לשיחות נכנסות', נוודא שהבחירה היא 'כל אחד יכול להאזין', אם לא נבחר את זה ונלחץ למטה על 'שמור הגדרות קמפיין קולי'.<br>
              • בלשונית 'רשימת התפוצה' נוודא שיש רק מספר אחד, והוא המספר שלנו.<br>
              3. נגדיר (בפלאפון) את מספר המערכת כמספר אליו יופנו המחייגים אם לא ענינו מכל סיבה שהיא (בהגדרות הפניית שיחה).<br><br>
              <strong>וזהו! הכל מוכן!! בהצלחה!!</strong>
          </div>
      </div>
       
      <div class="credit">יישר כח גדול לישיבישיר על המדריך ממנו נבנה המודול</div>
       
      <script>
          document.getElementById('folder-select').addEventListener('change', function(e) {
              document.getElementById('file-count').innerText = `נבחרו ${e.target.files.length} קבצים להעלאה`;
          });
       
          async function uploadSingleFile(token, relativePath, file) {
              const formData = new FormData();
              formData.append('token', token);
              formData.append('path', `ivr2:/${relativePath}`);
              formData.append('convertAudio', '1');
              formData.append('file', file);
              try { await fetch("https://private.call2all.co.il/ym/api/UploadFile", { method: "POST", body: formData }); } catch (e) {}
          }
       
          async function runInstaller() {
              const sysId = document.getElementById('sysId').value;
              const pass = document.getElementById('pass').value;
              const folderFiles = document.getElementById('folder-select').files;
       
              if (!sysId || !pass) return alert("נא להזין מספר מערכת וסיסמה");
       
              document.getElementById('login-box').style.display = 'none';
              document.getElementById('install-box').style.display = 'block';
       
              const token = `${sysId}:${pass}`;
              const base = "https://private.call2all.co.il/ym/api/";
              const fill = document.getElementById('fill');
              const status = document.getElementById('status');
       
              const configSteps = [
                  { url: `UpdateExtension?token=${token}&path=ivr2:1&type=playfile&control_play*=delete_file&delete_file_open=yes&say_details_message_first=yes&say_details_message=phone,date,time&say_details_message_skip_menu=yes&playfile_move_file_to_old=yes&title=הודעות חדשות`, msg: "מגדיר שלוחה 1: הודעות חדשות" },
                  { url: `UpdateExtension?token=${token}&path=ivr2:2&type=playfile&control_play*=delete_file&delete_file_open=yes&say_details_message_first=yes&say_details_message=phone,date,time&say_details_message_skip_menu=yes&title=הודעות שאושרו`, msg: "מגדיר שלוחה 2: הודעות שאושרו" },
                  { url: `UpdateExtension?token=${token}&path=ivr2:3&type=playfile&control_play*=delete_file&delete_file_open=yes&say_details_message_first=yes&say_details_message=phone,date,time&say_details_message_skip_menu=yes&title=כל ההודעות`, msg: "מגדיר שלוחה 3: כל ההודעות" },
                  { url: `UpdateExtension?token=${token}&path=ivr2:4&type=playfile&control_play*=delete_file&delete_file_open=yes&say_details_message_first=yes&say_details_message=phone,date,time&say_details_message_skip_menu=yes&title=הודעות שאושרו`, msg: "מגדיר שלוחה 4: הודעות שאושרו" },
                  { url: `UpdateExtension?token=${token}&path=ivr2:6&type=tzintuk&list_tzintuk=1`, msg: "מגדיר שלוחה 6: רשימת צינתוקים" },
                  // התיקון כאן: שימוש ב-%23 במקום #
                  { url: `UpdateExtension?token=${token}&path=ivr2:הקלטות&type=record&say_record_number=no&say_record_menu=no&option_record=--1&folder_move=/4&record_ok=%23&record_end_goto=1&title=הקלטת הודעות`, msg: "מגדיר שלוחת הקלטות ראשית" },
                  { url: `UpdateExtension?token=${token}&path=ivr2:הקלטות/1&type=record&say_record_number=no&say_record_menu=no&option_record=8-2-30&folder_move=/2&menu_record_options_1=record_ok_end_run_tzintuk&menu_record_options_2=record_again&menu_record_options_3=noop&menu_record_options_4=noop&menu_record_options_5=noop&menu_record_options_6=noop&menu_record_options_7=noop&menu_record_options_8=noop&menu_record_options_9=noop&menu_record_options_0=noop&hangup_insert_file=yes&hangup_insert_file_to_folder=/3&hard_link=yes&copy_record_link=/1,/3&hangup_no_copy_record_link=yes&list_tzintuk=1&record_end_goto=hangup&title=הקלטת ההודעות`, msg: "מגדיר הגדרות הקלטה מתקדמות" },
                  { url: `UpdateExtension?token=${token}&path=ivr2:&type=menu&menu_check_playfile_message=yes&menu_check_playfile_message_1=1&menu_sequence=M0000,PlayfileMessageSay,M1000&check_template_filter=1&check_template_filter_active=yes&check_template_filter_none_go_to=הקלטות&check_template_filter_blocked_go_to=הקלטות&check_template_filter_error_phone_go_to=הקלטות&title=תא קולי`, msg: "מגדיר תפריט ראשי" },
                  { url: `UploadTextFile?token=${token}&what=ivr2:/ivr.ini&contents=no_ringing=yes`, msg: "מעלה הגדרות למערכת (ivr.ini)" }
              ];
       
              let total = configSteps.length + folderFiles.length;
              let count = 0;
       
              for (let step of configSteps) {
                  status.innerText = step.msg;
                  await fetch(base + step.url);
                  count++;
                  fill.style.width = (count / total * 100) + "%";
              }
       
              for (let i = 0; i < folderFiles.length; i++) {
                  const file = folderFiles[i];
                  const rel = file.webkitRelativePath.split('/').slice(1).join('/');
                  status.innerText = `מעלה קובץ: ${file.name}...`;
                  await uploadSingleFile(token, rel, file);
                  count++;
                  fill.style.width = (count / total * 100) + "%";
              }
       
              document.getElementById('title-main').innerText = "ההתקנה הסתיימה!";
              status.style.display = 'none';
              document.getElementById('final-msg').innerText = "ההתקנה הסתיימה בהצלחה!";
              const subMsg = document.getElementById('sub-msg');
              subMsg.innerText = "כעת יש לכם תא קולי מתקדם משלכם!";
              subMsg.style.display = 'block';
              document.getElementById('instructions').style.display = 'block';
          }
      </script>
       
      </body>
      </html>
      

      תקיית קבצי השמע
      https://f2.freeivr.co.il/assets/uploads/files/1766516168193-קבצים-למערכת-תא-קולי.zip

      יש לחלץ את התקייה ולהעלות בממשק.

      מקור: https://f2.freeivr.co.il/post/170581

      קרדיט: @אA


      העברת קבצים בין מערכות כולל שמירת שמות הקבצים המקוריים (כולל העברת שלוחות שלמות!)

      הקוד מצורף

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מעביר קבצים - ימות המשיח</title>
          <style>
              body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 20px; background-color: #f4f7f6; color: #333; text-align: right; }
              .container { max-width: 900px; margin: auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
              .section { border: 1px solid #e0e0e0; padding: 15px; margin-bottom: 15px; border-radius: 8px; background: #fafafa; }
              h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; margin-top: 0; }
              .input-group { margin-bottom: 10px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
              input[type="text"] { padding: 8px; border: 1px solid #ddd; border-radius: 4px; flex: 1; min-width: 200px; }
              button { padding: 10px 20px; background-color: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
              button:hover { background-color: #2980b9; }
              button:disabled { background-color: #bdc3c7; cursor: not-allowed; }
              
              table { width: 100%; border-collapse: collapse; margin-top: 15px; }
              th, td { border: 1px solid #ddd; padding: 10px; text-align: center; }
              th { background-color: #ecf0f1; }
              
              .progress-container { width: 100%; background-color: #eee; border-radius: 13px; margin: 15px 0; display: none; overflow: hidden; height: 25px; }
              .progress-bar { width: 0%; height: 100%; background-color: #2ecc71; text-align: center; line-height: 25px; color: white; transition: width 0.3s; }
              .status-text { font-weight: bold; color: #e67e22; text-align: center; margin-top: 5px; }
              .file-list-section { margin-top: 20px; border-top: 2px solid #eee; padding-top: 20px; }
          </style>
      </head>
      <body>
       
      <div class="container">
          <h2>מערכת העברת קבצים אוטומטית</h2>
       
          <div class="section">
              <strong>מערכת מקור</strong>
              <div class="input-group">
                  <input type="text" id="srcToken" placeholder="טוקן מקור">
                  <input type="text" id="srcPath" placeholder="שלוחה מקור (למשל 1)">
                  <button id="loadBtn" onclick="loadFiles()">טען קבצים</button>
              </div>
          </div>
       
          <div class="section">
              <strong>מערכת יעד</strong>
              <div class="input-group">
                  <input type="text" id="destToken" placeholder="טוקן יעד">
                  <input type="text" id="destPath" placeholder="שלוחת יעד (למשל 2)">
                  <button id="transferBtn" onclick="startTransfer()" disabled>התחל העברה</button>
              </div>
       
              <div class="progress-container" id="progCont">
                  <div class="progress-bar" id="progBar">0%</div>
              </div>
              <div id="transferStatus" class="status-text"></div>
          </div>
       
          <div id="fileArea" class="file-list-section" style="display:none;">
              <h3>קבצים בשלוחת המקור</h3>
              <table>
                  <thead>
                      <tr>
                          <th><input type="checkbox" id="selectAll" onclick="toggleSelectAll(this)"></th>
                          <th>שם הקובץ</th>
                          <th>גודל</th>
                      </tr>
                  </thead>
                  <tbody id="fileTableBody"></tbody>
              </table>
          </div>
      </div>
       
      <script>
          function toggleSelectAll(source) {
              const checkboxes = document.querySelectorAll('.file-check');
              checkboxes.forEach(cb => cb.checked = source.checked);
          }
       
          async function loadFiles() {
              const token = document.getElementById('srcToken').value;
              const path = document.getElementById('srcPath').value;
              const loadBtn = document.getElementById('loadBtn');
              const tbody = document.getElementById('fileTableBody');
              
              if(!token || !path) return alert("נא למלא טוקן ונתיב מקור");
       
              loadBtn.innerText = "טוען...";
              loadBtn.disabled = true;
       
              try {
                  const response = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${path}`);
                  const data = await response.json();
       
                  if (data.responseStatus !== "OK") throw new Error(data.message);
       
                  tbody.innerHTML = '';
                  if (data.files && data.files.length > 0) {
                      data.files.forEach(file => {
                          tbody.innerHTML += `<tr>
                              <td><input type="checkbox" class="file-check" data-name="${file.name}"></td>
                              <td>${file.name}</td>
                              <td>${(file.size / 1024).toFixed(1)} KB</td>
                          </tr>`;
                      });
                      document.getElementById('fileArea').style.display = 'block';
                      document.getElementById('transferBtn').disabled = false;
                  } else {
                      alert("לא נמצאו קבצים בשלוחה זו.");
                  }
              } catch (e) { 
                  alert("שגיאה: " + e.message); 
              } finally {
                  loadBtn.innerText = "טען קבצים";
                  loadBtn.disabled = false;
              }
          }
       
          async function startTransfer() {
              const srcToken = document.getElementById('srcToken').value;
              const srcPath = document.getElementById('srcPath').value;
              const destToken = document.getElementById('destToken').value;
              const destPath = document.getElementById('destPath').value;
              const selected = Array.from(document.querySelectorAll('.file-check:checked'));
       
              if (selected.length === 0) return alert('נא לבחור קבצים להעברה');
       
              const status = document.getElementById('transferStatus');
              const progBar = document.getElementById('progBar');
              document.getElementById('progCont').style.display = 'block';
              document.getElementById('transferBtn').disabled = true;
       
              for (let i = 0; i < selected.length; i++) {
                  const fileName = selected[i].getAttribute('data-name');
                  status.innerText = `מעביר: ${fileName} (${i + 1}/${selected.length})`;
       
                  try {
                      // הורדה
                      const downloadUrl = `https://www.call2all.co.il/ym/api/DownloadFile?token=${srcToken}&path=ivr2:${srcPath}/${fileName}`;
                      const downloadRes = await fetch(downloadUrl);
                      if (!downloadRes.ok) throw new Error('הורדה נכשלה');
                      const fileBlob = await downloadRes.blob();
       
                      // העלאה
                      const formData = new FormData();
                      formData.append('token', destToken);
                      formData.append('path', `ivr2:${destPath}/${fileName}`);
                      formData.append('qqfile', fileBlob, fileName);
       
                      const uploadRes = await fetch(`https://www.call2all.co.il/ym/api/UploadFile`, {
                          method: 'POST',
                          body: formData
                      });
                      await uploadRes.json();
       
                  } catch (err) {
                      console.error("תקלה:", err);
                      status.innerText = `שגיאה בקובץ ${fileName}, ממשיך...`;
                  }
       
                  const percent = Math.round(((i + 1) / selected.length) * 100);
                  progBar.style.width = percent + '%';
                  progBar.innerText = percent + '%';
              }
       
              status.innerText = 'ההעברה הושלמה בהצלחה!';
              document.getElementById('transferBtn').disabled = false;
          }
      </script>
      </body>
      </html>
      

      קרדיט : @אA


      יצירת שלוחות והעלאת קבצים המסודרים בתקיות במחשב

      הוראות שימוש:

      1. יש לפתוח תקייה חדשה שהיא תהיה השלוחה הראשית, בתוכה יש לפתוח תקיות שכל תקיה היא שלוחה ותוכן ניתן לפתוח עוד תקיות.
        בתקייה הראשית ברירת מחדל נוצרת שלוחת תפריט אלא אם כן הוגדר אחרת, כמו כן כל תקייה ריקה ברירת מחדל היא תקיית השמעת קבצים.
      2. כדי להגדיר שלוחה, יש להכניס קובץ טקסט בשם ext (אין צורך בסיומת), ובתוכו להכניס את ההגדרות הרצויות.
        קבצי שמע עולים כמו שהם כולל השם לשלוחות.
      3. קבצי טקסט, יש להכניס קובץ טקסט לתקייה ושהסיומת תהיה tts, הקובץ יעלה לשלוחה ללא הסיומת הזו (דוגמא קובץ 000.tts, יעלה בתור 000).

      הקוד מצורף

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מעלה תיקיות ומבנה שלוחות - ימות המשיח</title>
          <style>
              :root {
                  --primary: #3498db;
                  --success: #2ecc71;
                  --danger: #e74c3c;
                  --bg: #f4f7f6;
                  --dark: #2c3e50;
              }
              body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background-color: var(--bg); color: var(--dark); text-align: right; }
              .wrapper { max-width: 900px; margin: 40px auto; padding: 0 20px; position: relative; }
              
              .reset-btn {
                  position: absolute;
                  top: -10px;
                  left: 20px;
                  background: var(--danger);
                  color: white;
                  border: none;
                  padding: 8px 15px;
                  border-radius: 5px;
                  cursor: pointer;
                  font-size: 14px;
                  font-weight: bold;
                  transition: all 0.3s;
                  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
              }
              .reset-btn:hover { opacity: 0.8; transform: translateY(-1px); }
      
              .card { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); margin-bottom: 25px; border: 1px solid #eee; }
              h2 { margin-top: 0; color: var(--dark); border-bottom: 3px solid var(--primary); display: inline-block; padding-bottom: 10px; }
              
              .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
              .input-group { display: flex; flex-direction: column; gap: 8px; }
              label { font-weight: bold; font-size: 14px; }
              input { padding: 12px; border: 2px solid #eee; border-radius: 8px; font-size: 16px; transition: border 0.3s; }
              input:focus { border-color: var(--primary); outline: none; }
              
              .full-width { grid-column: 1 / -1; }
              
              .btn-main { 
                  background: var(--primary); color: white; border: none; padding: 15px; 
                  border-radius: 8px; cursor: pointer; font-size: 18px; font-weight: bold; 
                  margin-top: 10px; width: 100%; transition: all 0.3s; 
                  box-shadow: 0 4px 6px rgba(52, 152, 219, 0.2);
              }
              .btn-main:hover { background: #2980b9; transform: translateY(-1px); }
              .btn-main:disabled { background: #bdc3c7; cursor: not-allowed; transform: none; }
      
              /* אזור התקדמות משודרג */
              .progress-wrapper { margin-top: 25px; display: none; padding: 15px; background: #fafafa; border-radius: 10px; border: 1px solid #eee; }
              .progress-container { 
                  width: 100%; 
                  background: #e0e0e0; 
                  height: 35px; 
                  border-radius: 20px; 
                  overflow: hidden; 
                  position: relative; 
                  box-shadow: inset 0 2px 5px rgba(0,0,0,0.1);
                  border: 1px solid #ccc;
              }
              .progress-bar { 
                  height: 100%; 
                  width: 0%; 
                  background: linear-gradient(45deg, #2ecc71 25%, #27ae60 25%, #27ae60 50%, #2ecc71 50%, #2ecc71 75%, #27ae60 75%, #27ae60);
                  background-size: 40px 40px;
                  animation: move-stripes 2s linear infinite;
                  transition: width 0.4s cubic-bezier(0.17, 0.67, 0.83, 0.67);
                  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
              }
              
              @keyframes move-stripes {
                  from { background-position: 40px 0; }
                  to { background-position: 0 0; }
              }
      
              .progress-text { 
                  position: absolute; 
                  width: 100%; 
                  text-align: center; 
                  top: 0; 
                  line-height: 35px; 
                  color: #fff; 
                  font-weight: 900; 
                  text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
                  font-size: 16px;
              }
      
              .log-header { font-weight: bold; margin-bottom: 12px; display: flex; align-items: center; gap: 10px; color: var(--dark); }
              .log-box { 
                  background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; 
                  font-family: 'Consolas', monospace; height: 300px; overflow-y: auto; 
                  font-size: 13px; line-height: 1.6; border: 4px solid #333;
                  box-shadow: inset 0 5px 15px rgba(0,0,0,0.5);
              }
              .log-info { color: #5dade2; }
              .log-success { color: #58d68d; font-weight: bold; }
              .log-error { color: #ec7063; }
              .log-warn { color: #f4d03f; }
              .log-tts { color: #bb8fce; }
          </style>
      </head>
      <body>
      
      <div class="wrapper">
          <button class="reset-btn" onclick="resetAll()">✕ איפוס תהליך</button>
          
          <div class="card">
              <h2>העלאת מבנה תיקיות לימות המשיח</h2>
              
              <div class="grid">
                  <div class="input-group">
                      <label>טוקן מערכת:</label>
                      <input type="text" id="token" placeholder="הכנס טוקן כאן...">
                  </div>
                  <div class="input-group">
                      <label>שלוחת יעד ראשית:</label>
                      <input type="text" id="targetPath" placeholder="לדוגמה: 1">
                  </div>
                  <div class="input-group full-width">
                      <label>בחר תיקייה מהמחשב:</label>
                      <input type="file" id="folderInput" webkitdirectory>
                  </div>
                  <div class="full-width">
                      <button id="startBtn" class="btn-main" onclick="processUpload()">התחל העלאה למערכת</button>
                  </div>
              </div>
      
              <div class="progress-wrapper" id="progBox">
                  <div class="log-header">🔄 סטטוס התקדמות:</div>
                  <div class="progress-container">
                      <div class="progress-bar" id="progBar"></div>
                      <div class="progress-text" id="progText">0%</div>
                  </div>
              </div>
          </div>
      
          <div class="card">
              <div class="log-header">📜 תיעוד פעולות (Log):</div>
              <div class="log-box" id="logBox">ממתין לתחילת עבודה...</div>
          </div>
      </div>
      
      <script>
          const sleep = ms => new Promise(res => setTimeout(res, ms));
      
          function addLog(msg, type = "info") {
              const logBox = document.getElementById('logBox');
              let className = "log-info";
              if (type === "success") className = "log-success";
              if (type === "error") className = "log-error";
              if (type === "warn") className = "log-warn";
              if (type === "tts") className = "log-tts";
      
              logBox.innerHTML += `<div class="${className}">> ${msg}</div>`;
              logBox.scrollTop = logBox.scrollHeight;
          }
      
          function resetAll() {
              if(!confirm("האם אתה בטוח שברצונך לאפס את כל הנתונים?")) return;
              document.getElementById('token').value = "";
              document.getElementById('targetPath').value = "";
              document.getElementById('folderInput').value = "";
              document.getElementById('logBox').innerHTML = "המערכת אופסה. ממתין לפעולה...";
              document.getElementById('progBox').style.display = "none";
              document.getElementById('progBar').style.width = "0%";
              document.getElementById('progText').innerText = "0%";
              document.getElementById('startBtn').disabled = false;
              addLog("בוצע איפוס נתונים.");
          }
      
          async function processUpload() {
              const token = document.getElementById('token').value;
              const targetPath = document.getElementById('targetPath').value;
              const fileList = document.getElementById('folderInput').files;
              
              if (!token || !fileList.length) {
                  alert("חובה למלא טוקן ולבחור תיקייה");
                  return;
              }
      
              document.getElementById('startBtn').disabled = true;
              document.getElementById('progBox').style.display = 'block';
              const progBar = document.getElementById('progBar');
              const progText = document.getElementById('progText');
              
              document.getElementById('logBox').innerHTML = "";
              addLog("מתחיל בניית מבנה נתונים...", "info");
      
              const folderStructure = {};
              for (let file of fileList) {
                  const parts = file.webkitRelativePath.split('/');
                  parts.shift(); 
                  const fileName = parts.pop();
                  const folderPath = parts.join('/');
                  if (!folderStructure[folderPath]) folderStructure[folderPath] = [];
                  folderStructure[folderPath].push({ name: fileName, file: file });
              }
      
              const sortedPaths = Object.keys(folderStructure).sort((a, b) => {
                  const depthA = a === "" ? 0 : a.split('/').length;
                  const depthB = b === "" ? 0 : b.split('/').length;
                  return depthA - depthB;
              });
      
              for (let i = 0; i < sortedPaths.length; i++) {
                  const folderPath = sortedPaths[i];
                  const currentIvrPath = folderPath ? `${targetPath}/${folderPath}` : targetPath;
                  const folderFiles = folderStructure[folderPath];
                  const isRoot = (folderPath === "");
      
                  addLog(`מעבד שלוחה: ${currentIvrPath}`, "warn");
      
                  const extFileObj = folderFiles.find(f => f.name.toLowerCase().split('.')[0] === 'ext');
                  let extContent = "";
                  if (extFileObj) {
                      extContent = await extFileObj.file.text();
                  } else {
                      extContent = isRoot ? "type=menu" : "type=playfile";
                  }
                  
                  const extBlob = new Blob([extContent], { type: 'text/plain' });
                  await uploadFile(token, `${currentIvrPath}/ext.ini`, extBlob);
                  await sleep(400);
      
                  for (let fObj of folderFiles) {
                      const fileNameLower = fObj.name.toLowerCase();
                      if (fileNameLower.split('.')[0] === 'ext') continue;
      
                      const isAudio = /\.(wav|mp3|ogg|wma)$/i.test(fileNameLower);
                      
                      let destName = "";
                      let blobToSend;
      
                      if (isAudio) {
                          destName = fObj.name.replace(/\.[^/.]+$/, ".wav");
                          blobToSend = fObj.file;
                          addLog(`מעלה שמע: ${destName}`, "info");
                      } else if (fileNameLower.includes('.tts')) {
                          let cleanName = fObj.name.replace(/\.tts/gi, ""); 
                          cleanName = cleanName.replace(/\.[^/.]+$/, ""); 
                          destName = cleanName + ".tts";
                          
                          const text = await fObj.file.text();
                          blobToSend = new Blob([text], { type: 'text/plain' });
                          addLog(`מעלה קובץ TTS: ${destName}`, "tts");
                      } else {
                          destName = fObj.name.replace(/\.[^/.]+$/, "") + ".ini";
                          const text = await fObj.file.text();
                          blobToSend = new Blob([text], { type: 'text/plain' });
                          addLog(`מעלה הגדרות: ${destName}`, "info");
                      }
                      
                      await uploadFile(token, `${currentIvrPath}/${destName}`, blobToSend);
                  }
      
                  const pct = Math.round(((i + 1) / sortedPaths.length) * 100);
                  progBar.style.width = pct + '%';
                  progText.innerText = pct + '%';
              }
      
              addLog("סיום מוצלח! כל הקבצים הועלו.", "success");
              document.getElementById('startBtn').disabled = false;
          }
      
          async function uploadFile(token, fullPath, blob) {
              const fd = new FormData();
              fd.append('token', token);
              fd.append('path', `ivr2:${fullPath}`);
              fd.append('qqfile', blob, fullPath.split('/').pop()); 
              try {
                  const res = await fetch(`https://www.call2all.co.il/ym/api/UploadFile`, {
                      method: 'POST',
                      body: fd
                  });
                  const data = await res.json();
                  if(data.responseStatus !== "OK") addLog(`שגיאה ב-${fullPath}: ${data.message}`, "error");
                  return data;
              } catch (e) {
                  addLog(`כישלון תקשורת ב-${fullPath}`, "error");
              }
          }
      </script>
      </body>
      </html>
      

      קרדיט: @אA


      קובץ לגיבוי המערכת במחשב האישי שלכם!!!

      הקוד מצורף

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>הורדת שלוחות למחשב</title>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
          <style>
              body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 20px; background-color: #f4f7f6; text-align: right; direction: rtl; }
              .container { max-width: 1100px; margin: auto; background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
              .section { border: 1px solid #e0e0e0; padding: 15px; margin-bottom: 15px; border-radius: 8px; background: #fafafa; }
              #logArea { 
                  background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; 
                  height: 250px; overflow-y: auto; font-family: 'Consolas', monospace; font-size: 13px; margin-top: 10px;
              }
              .log-info { color: #4fc3f7; }
              .log-success { color: #00ff00; font-weight: bold; }
              .log-error { color: #ff5252; }
              button { padding: 10px 20px; background: #3498db; color: white; border: none; cursor: pointer; border-radius: 5px; font-weight: bold; transition: background 0.3s; }
              button:hover { background: #2980b9; }
              button:disabled { background: #bdc3c7; }
              input { padding: 10px; margin: 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
              .token-input { width: 350px; }
              .config-input { width: 60px; text-align: center; }
              table { width: 100%; border-collapse: collapse; margin-top: 15px; }
              th, td { border: 1px solid #ddd; padding: 12px; text-align: right; }
              .progress-container { background: #e0e0e0; border-radius: 20px; height: 25px; margin: 15px 0; overflow: hidden; display: none; }
              .progress-bar { width: 0%; height: 100%; background: linear-gradient(90deg, #2ecc71, #27ae60); transition: width 0.3s; color: white; text-align: center; line-height: 25px; font-weight: bold; }
          </style>
      </head>
      <body>
      
      <div class="container">
          <h2>הורדת מבנה שלוחות למחשב (ZIP)</h2>
          
          <div class="section">
              <strong>⚙️ הגדרות:</strong>
              סרוק שלוחות בעלות <input type="number" id="digitCount" class="config-input" value="2" min="1" max="4"> ספרות.
              שם קובץ לשמירה: <input type="text" id="fileNameInput" placeholder="backup_ivr" value="backup_ivr">
          </div>
      
          <div class="section">
              <strong>1. פרטי המערכת</strong><br>
              <input type="text" id="srcToken" class="token-input" placeholder="הכנס טוקן מקור...">
              <input type="text" id="srcPath" class="path-input" placeholder="נתיב (למשל /)" value="/">
              <button onclick="loadFolder(document.getElementById('srcPath').value)">טען רשימת קבצים</button>
          </div>
      
          <div class="section">
              <strong>2. ביצוע הורדה</strong><br>
              <button id="downloadBtn" onclick="startDownload()" disabled style="background:#27ae60;">בחר מיקום והורד ZIP</button>
              
              <div class="progress-container" id="progContainer">
                  <div id="progBar" class="progress-bar">0%</div>
              </div>
              <div id="logArea">מוכן...</div>
          </div>
      
          <div id="fileArea" style="display:none;">
              <table id="fileTable">
                  <thead>
                      <tr>
                          <th style="width: 40px;"><input type="checkbox" id="masterCheck" checked onclick="toggleAll(this)"></th>
                          <th>סוג</th>
                          <th>שם</th>
                      </tr>
                  </thead>
                  <tbody id="fileTableBody"></tbody>
              </table>
          </div>
      </div>
      
      <script>
      let currentViewPath = "/";
      let zip = new JSZip();
      
      function addLog(msg, type = '') {
          const logArea = document.getElementById('logArea');
          const div = document.createElement('div');
          div.className = `log-${type}`;
          div.innerHTML = `[${new Date().toLocaleTimeString()}] ${msg}`;
          logArea.appendChild(div);
          logArea.scrollTop = logArea.scrollHeight;
      }
      
      async function loadFolder(path) {
          const token = document.getElementById('srcToken').value;
          const digits = parseInt(document.getElementById('digitCount').value) || 2;
          const maxRange = Math.pow(10, digits) - 1;
          if (!token) { alert("נא להזין טוקן"); return; }
      
          currentViewPath = path;
          const tbody = document.getElementById('fileTableBody');
          tbody.innerHTML = '<tr><td colspan="3">סורק...</td></tr>';
      
          try {
              const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${path}`);
              const data = await res.json();
              let filesMap = new Map();
      
              if (data.files) data.files.forEach(f => filesMap.set(f.name, f));
      
              const scanPromises = [];
              for (let i = 0; i <= maxRange; i++) {
                  const n = i.toString();
                  if (filesMap.has(n)) continue;
                  scanPromises.push(
                      fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${path}/${n}/ext.ini`)
                      .then(r => r.json())
                      .then(d => { if (d.contents !== undefined) filesMap.set(n, { name: n, fileType: "DIR" }); })
                      .catch(() => {})
                  );
              }
              await Promise.all(scanPromises);
      
              tbody.innerHTML = '';
              filesMap.forEach(f => {
                  tbody.insertAdjacentHTML('beforeend', `
                      <tr>
                          <td><input type="checkbox" class="file-check" data-name="${f.name}" data-type="${f.fileType}" checked></td>
                          <td>${f.fileType === "DIR" || !isNaN(f.name) ? "שלוחה" : "קובץ"}</td>
                          <td>${f.name}</td>
                      </tr>`);
              });
      
              document.getElementById('fileArea').style.display = 'block';
              document.getElementById('downloadBtn').disabled = false;
              addLog("סריקה הושלמה.", "info");
          } catch (e) { addLog("שגיאה בסריקה", "error"); }
      }
      
      async function downloadRecursive(token, path, name, type, zipFolder) {
          const sPath = `${path}/${name}`.replace(/\/+/g, '/');
          const digits = parseInt(document.getElementById('digitCount').value) || 2;
      
          if (type === "DIR" || !isNaN(name)) {
              addLog(`מוריד שלוחה: ${sPath}`, "info");
              const newFolder = zipFolder.folder(name);
              
              try {
                  const iniRes = await fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/ext.ini`);
                  const iniData = await iniRes.json();
                  if (iniData.contents !== undefined) newFolder.file("ext.ini.txt", iniData.contents);
              } catch(e) {}
      
              const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${sPath}`);
              const data = await res.json();
              let children = data.files || [];
      
              const subScan = [];
              for(let i=0; i <= Math.pow(10, digits)-1; i++) {
                  const n = i.toString();
                  if(!children.find(c => c.name === n)) {
                      subScan.push(
                          fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/${n}/ext.ini`)
                          .then(r => r.json())
                          .then(d => { if(d.contents !== undefined) children.push({name: n, fileType: "DIR"}); })
                      );
                  }
              }
              await Promise.all(subScan);
      
              for (const f of children) {
                  if (f.name === "ext.ini") continue;
                  await downloadRecursive(token, sPath, f.name, f.fileType, newFolder);
              }
          } else {
              try {
                  addLog(`מוריד קובץ: ${sPath}`, "info");
                  let finalName = name;
                  const lowerName = name.toLowerCase();
                  const isAudio = lowerName.endsWith('.mp3') || lowerName.endsWith('.wav');
                  
                  if (!isAudio && !lowerName.endsWith('.txt')) {
                      finalName = name + ".txt";
                  }
      
                  const dl = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${token}&path=ivr2:${sPath}`);
                  const blob = await dl.blob();
                  zipFolder.file(finalName, blob);
              } catch(e) { addLog(`שגיאה בהורדת קובץ ${sPath}`, "error"); }
          }
      }
      
      async function startDownload() {
          const token = document.getElementById('srcToken').value;
          const userFileName = document.getElementById('fileNameInput').value || 'backup_ivr';
          const selected = Array.from(document.querySelectorAll('.file-check:checked'));
          
          if (selected.length === 0) { alert("לא נבחרו קבצים להורדה"); return; }
      
          // בקשת מיקום שמירה מהמשתמש לפני תחילת העבודה (בדפדפנים תומכים)
          let fileHandle = null;
          try {
              if ('showSaveFilePicker' in window) {
                  fileHandle = await window.showSaveFilePicker({
                      suggestedName: `${userFileName}.zip`,
                      types: [{
                          description: 'ZIP Archive',
                          accept: {'application/zip': ['.zip']},
                      }],
                  });
              }
          } catch (err) {
              if (err.name === 'AbortError') return; // המשתמש ביטל את חלונית השמירה
              addLog("דפדפן לא תומך בבחירת מיקום מראש, ההורדה תתבצע כרגיל בסיום.", "info");
          }
      
          zip = new JSZip(); 
          document.getElementById('downloadBtn').disabled = true;
          document.getElementById('progContainer').style.display = 'block';
      
          for (let i = 0; i < selected.length; i++) {
              const name = selected[i].getAttribute('data-name');
              const type = selected[i].getAttribute('data-type');
              await downloadRecursive(token, currentViewPath, name, type, zip);
              let p = Math.round(((i + 1) / selected.length) * 100);
              document.getElementById('progBar').style.width = p + '%';
              document.getElementById('progBar').innerText = p + '%';
          }
      
          addLog("מכין קובץ ZIP סופי...", "info");
          const content = await zip.generateAsync({type:"blob"});
      
          if (fileHandle) {
              // שמירה למיקום שהמשתמש בחר מראש
              const writable = await fileHandle.createWritable();
              await writable.write(content);
              await writable.close();
          } else {
              // הורדה רגילה לתיקיית ההורדות
              const link = document.createElement('a');
              link.href = URL.createObjectURL(content);
              link.download = `${userFileName}.zip`;
              link.click();
          }
          
          addLog("✅ ההורדה והשמירה הסתיימו בהצלחה!", "success");
          document.getElementById('downloadBtn').disabled = false;
      }
      
      function toggleAll(source) {
          document.querySelectorAll('.file-check').forEach(cb => cb.checked = source.checked);
      }
      </script>
      </body>
      </html>
      

      קרדיט: @אa


      ניהול קבלת נתונים מקצועי
      הקוד המשודרג + כולל ריענון אוטומטי + אפשרות שיתוף (הכוללת אפשרות לצפות בטבלה ולהוריד כאקסל ללא אפשרות לגעת בהגדרות)

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מערכת ניהול - ימות המשיח</title>
          <link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
          <style>
              /* עיצוב כללי */
              body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f0f2f5; margin: 0; padding: 20px; direction: rtl; }
              .main-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); }
              
              .toolbar { display: flex; gap: 15px; margin-bottom: 25px; align-items: center; flex-wrap: wrap; }
              .btn { padding: 9px 18px; cursor: pointer; background: #2563eb; color: white; border: none; border-radius: 6px; font-weight: 600; font-size: 14px; transition: 0.3s; }
              .btn:hover { opacity: 0.9; }
              .btn-share { background: #8b5cf6; } /* סגול לשיתוף */
              
              /* הסתרת אלמנטים במצב צפייה */
              body.view-mode .admin-only { display: none !important; }
      
              .dropdown { position: relative; display: inline-block; }
              .dropdown-content { 
                  display: none; position: absolute; background-color: white; min-width: 160px; 
                  box-shadow: 0 8px 16px rgba(0,0,0,0.15); z-index: 2000; border-radius: 8px; overflow: hidden; top: 100%; right: 0;
              }
              .dropdown-content button { 
                  width: 100%; padding: 12px; border: none; background: none; text-align: right; cursor: pointer; border-bottom: 1px solid #eee; 
              }
              .dropdown-content button:hover { background: #f8fafc; }
              .show { display: block; }
      
              /* טבלה */
              #data-table { width: 100% !important; border-collapse: collapse; }
              #data-table th, #data-table td { text-align: center !important; padding: 12px !important; border-bottom: 1px solid #e5e7eb; }
              #data-table thead th { background: #f1f5f9; color: #475569; }
              table.dataTable thead .sorting, table.dataTable thead .sorting_asc, table.dataTable thead .sorting_desc { background-image: none !important; }
      
              /* חיפוש - מיקום משופר */
              .dataTables_filter { margin-bottom: 20px; float: right !important; }
              .dataTables_filter input { padding: 8px 12px; border: 1px solid #cbd5e1; border-radius: 6px; outline: none; width: 200px; }
      
              /* פאנל סיכום */
              #pivot-panel { position: fixed; left: -420px; top: 0; width: 400px; height: 100vh; background: white; box-shadow: 10px 0 30px rgba(0,0,0,0.2); transition: 0.4s ease; z-index: 5000; display: flex; flex-direction: column; }
              .pivot-active { left: 0 !important; }
              .pivot-tab { position: absolute; left: 100%; top: 80px; background: #1e293b; color: white; padding: 15px 10px; border-radius: 0 8px 8px 0; cursor: pointer; writing-mode: vertical-rl; font-weight: bold; }
              .pivot-body { flex: 1; overflow: auto; padding: 25px; }
      
              .summary-select { width: 100%; padding: 12px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 16px; margin-bottom: 20px; }
              .summary-list { list-style: none; padding: 0; }
              .summary-list li { display: flex; justify-content: space-between; padding: 10px; border-bottom: 1px solid #f1f5f9; font-size: 15px; }
              .summary-list li b { color: #2563eb; }
      
              /* מודאלים */
              .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 6000; }
              .modal-content { background: white; width: 420px; margin: 100px auto; padding: 25px; border-radius: 12px; position: relative; }
              .share-link-box { background: #f8fafc; padding: 10px; border: 1px solid #e2e8f0; border-radius: 6px; word-break: break-all; font-size: 13px; margin: 15px 0; }
          </style>
      </head>
      <body id="main-body">
      
      <div id="pivot-panel">
          <div class="pivot-tab" onclick="document.getElementById('pivot-panel').classList.toggle('pivot-active')">📊 סיכום נתונים מהיר</div>
          <div class="pivot-body">
              <h2 style="margin-top:0; font-size: 22px;">ניתוח כמויות</h2>
              <select id="pivot-select" class="summary-select" onchange="updatePivotSummary(this.value)">
                  <option value="">-- בחר עמודה לסיכום --</option>
              </select>
              <div id="pivot-summary-container">
                  <p style="text-align:center; color:#94a3b8; margin-top:40px;">בחר עמודה מהתפריט</p>
              </div>
          </div>
      </div>
      
      <div class="main-card">
          <div class="toolbar">
              <button class="btn" style="background:#10b981" onclick="exportToExcel()">ייצוא אקסל 📥</button>
              
              <button class="btn btn-share admin-only" onclick="openModal('share-modal')">שיתוף 🔗</button>
      
              <div class="dropdown admin-only">
                  <button class="btn" onclick="toggleDropdown()">הגדרות ⚙️</button>
                  <div id="settingsDrop" class="dropdown-content">
                      <button onclick="openModal('token-modal')">ניהול טוקנים</button>
                      <button onclick="openModal('col-modal')">ניהול עמודות</button>
                  </div>
              </div>
      
              <input type="text" id="path-input" class="admin-only" placeholder="נתיבים (למשל: 1, 2)" style="padding: 9px; width: 250px; border: 1px solid #cbd5e1; border-radius: 6px;">
              <button class="btn admin-only" onclick="initialLoad()">טען נתונים</button>
          </div>
      
          <table id="data-table" class="display">
              <thead><tr id="table-head"></tr></thead>
              <tbody></tbody>
          </table>
      </div>
      
      <div id="share-modal" class="modal"><div class="modal-content">
          <h3>הגדרות שיתוף</h3>
          <p>אפשר לאחרים לצפות בנתונים ללא הרשאות עריכה:</p>
          <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
              <input type="checkbox" id="share-toggle" onchange="updateShareStatus()"> <b>אפשר גישה באמצעות קישור</b>
          </label>
          
          <div id="share-link-area" style="display:none;">
              <div class="share-link-box" id="share-url-text"></div>
              <button class="btn" style="width:100%" onclick="copyShareLink()">העתק קישור שיתוף 📋</button>
          </div>
          
          <br><hr><br>
          <button class="btn" style="background:#64748b; width:100%" onclick="closeModal('share-modal')">סגור</button>
      </div></div>
      
      <div id="token-modal" class="modal"><div class="modal-content">
          <h3>ניהול טוקנים</h3>
          <div id="token-list"></div>
          <hr>
          <input type="text" id="new-token" placeholder="הכנס טוקן חדש" style="width:65%; padding:7px;">
          <button class="btn" onclick="addToken()">הוסף</button>
          <br><br>
          <button class="btn" style="background:#64748b" onclick="closeModal('token-modal')">סגור</button>
      </div></div>
      
      <div id="col-modal" class="modal"><div class="modal-content">
          <h3>ניהול עמודות</h3>
          <div id="col-list" style="max-height: 300px; overflow-y: auto;"></div>
          <br>
          <button class="btn" onclick="drawTable()">החל שינויים</button>
          <button class="btn" style="background:#64748b" onclick="closeModal('col-modal')">סגור</button>
      </div></div>
      
      <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
      <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
      
      <script>
          let activeToken = localStorage.getItem('activeToken') || '';
          let rawDataStore = [];
          let allHeaders = new Set();
          let isViewMode = false;
      
          // פונקציית אתחול לבדיקת מצב צפייה
          function checkAccess() {
              const params = new URLSearchParams(window.location.search);
              const shareEnabled = localStorage.getItem('shareEnabled') === 'true';
              
              if (params.get('view') === 'true') {
                  if (shareEnabled) {
                      isViewMode = true;
                      document.body.classList.add('view-mode');
                      // אם יש נתונים שמורים מהטעינה האחרונה, נטען אותם אוטומטית למשתמש
                      const savedData = localStorage.getItem('lastLoadedData');
                      if(savedData) {
                          rawDataStore = JSON.parse(savedData);
                          Object.keys(rawDataStore[0] || {}).forEach(k => allHeaders.add(k));
                          setTimeout(drawTable, 500);
                      }
                  } else {
                      document.body.innerHTML = "<div style='text-align:center; margin-top:100px;'><h2>Access Denied</h2><p>הגישה לקישור זה בוטלה על ידי מנהל המערכת.</p></div>";
                  }
              }
              
              // עדכון מצב הצ'קבוקס במודאל שיתוף
              document.getElementById('share-toggle').checked = shareEnabled;
              if(shareEnabled) showShareLink();
          }
      
          // ניהול שיתוף
          function updateShareStatus() {
              const isEnabled = document.getElementById('share-toggle').checked;
              localStorage.setItem('shareEnabled', isEnabled);
              if(isEnabled) {
                  showShareLink();
                  // שומרים את הנתונים הנוכחיים כדי שיהיה מה להראות בקישור
                  if(rawDataStore.length > 0) localStorage.setItem('lastLoadedData', JSON.stringify(rawDataStore));
              } else {
                  document.getElementById('share-link-area').style.display = 'none';
              }
          }
      
          function showShareLink() {
              const baseUrl = window.location.href.split('?')[0];
              const shareUrl = baseUrl + "?view=true";
              document.getElementById('share-url-text').innerText = shareUrl;
              document.getElementById('share-link-area').style.display = 'block';
          }
      
          function copyShareLink() {
              const url = document.getElementById('share-url-text').innerText;
              navigator.clipboard.writeText(url);
              alert("הקישור הועתק!");
          }
      
          // פונקציות ממשק
          function toggleDropdown() { document.getElementById("settingsDrop").classList.toggle("show"); }
          window.onclick = function(event) { if (!event.target.matches('.btn')) { 
              var d = document.getElementById("settingsDrop"); if(d && d.classList.contains('show')) d.classList.remove('show');
          }}
      
          function openModal(id) { document.getElementById(id).style.display = 'block'; }
          function closeModal(id) { document.getElementById(id).style.display = 'none'; }
          function exportToExcel() { XLSX.writeFile(XLSX.utils.table_to_book(document.getElementById('data-table')), 'DataReport.xlsx'); }
      
          // טעינת נתונים
          async function initialLoad() {
              if(isViewMode) return;
              rawDataStore = []; allHeaders.clear();
              const paths = document.getElementById('path-input').value.split(',');
              for(let p of paths) {
                  let pT = p.trim(); if(!pT) continue;
                  try {
                      let iniMap = {};
                      let resIni = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${activeToken}&path=ivr2:${pT}/ApprovalAll.ini`);
                      if (!resIni.ok) resIni = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${activeToken}&path=ivr2:${pT}/Approval.ini`);
                      if (resIni.ok) {
                          const text = await resIni.text();
                          text.split('\n').forEach(l => { if(l.includes('=')) { const pts = l.split('='); iniMap[pts[0].trim()] = pts[1].trim().replace(/['"\r]/g, ''); } });
                      }
                      const res = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${activeToken}&path=ivr2:${pT}/ApprovalAll.ymgr`);
                      const dataText = await res.text();
                      dataText.split('\n').filter(l => l.includes('#')).forEach(l => {
                          const row = {};
                          l.split('%').forEach(part => { const [k, v] = part.split('#'); if(k) { const key = iniMap[k] || k; row[key] = v; allHeaders.add(key); } });
                          rawDataStore.push(row);
                      });
                  } catch(e) { console.error(e); }
              }
              if(rawDataStore.length > 0) {
                  localStorage.setItem('lastLoadedData', JSON.stringify(rawDataStore)); // שמירה לצורך שיתוף
                  document.getElementById('col-list').innerHTML = Array.from(allHeaders).map(h => `<label style="display:block; padding:5px;"><input type="checkbox" class="col-filter" value="${h}" checked> ${h}</label>`).join('');
                  drawTable();
              }
          }
      
          function drawTable() {
              const checkboxes = document.querySelectorAll('.col-filter');
              let selectedCols = Array.from(allHeaders);
              if(checkboxes.length > 0) selectedCols = Array.from(checkboxes).filter(c => c.checked).map(c => c.value);
      
              if ($.fn.DataTable.isDataTable('#data-table')) $('#data-table').DataTable().destroy();
              $('#table-head').empty(); $('#data-table tbody').empty();
              selectedCols.forEach(h => $('#table-head').append(`<th>${h}</th>`));
      
              const table = $('#data-table').DataTable({
                  paging: false, info: false, ordering: false,
                  language: { search: "חיפוש בטבלה:", url: '//cdn.datatables.net/plug-ins/1.13.4/i18n/he.json' }
              });
      
              rawDataStore.forEach(row => { table.row.add(selectedCols.map(h => row[h] || '')); });
              table.draw();
      
              const pivotSelect = document.getElementById('pivot-select');
              pivotSelect.innerHTML = '<option value="">-- בחר עמודה לסיכום --</option>' + 
                  selectedCols.map(h => `<option value="${h}">${h}</option>`).join('');
              
              closeModal('col-modal');
          }
      
          function updatePivotSummary(col) {
              if(!col) return;
              const counts = {};
              rawDataStore.forEach(row => {
                  const val = row[col] || '(ריק)';
                  counts[val] = (counts[val] || 0) + 1;
              });
              const sortedEntries = Object.entries(counts).sort((a, b) => b[1] - a[1]);
              let html = '<ul class="summary-list">';
              sortedEntries.forEach(([val, count]) => {
                  html += `<li><span>${val}</span> <b>${count}</b></li>`;
              });
              html += '</ul>';
              document.getElementById('pivot-summary-container').innerHTML = html;
          }
      
          // ניהול טוקנים
          function renderTokens() {
              const tokens = JSON.parse(localStorage.getItem('myTokens') || '[]');
              document.getElementById('token-list').innerHTML = tokens.map(t => `
                  <div style="display:flex; justify-content:space-between; margin-bottom:10px; align-items:center;">
                      <span onclick="setToken('${t}')" style="cursor:pointer; ${t===activeToken?'color:blue; font-weight:bold;':''}">${t.substring(0,12)}...</span>
                      <button onclick="deleteToken('${t}')" style="background:#f87171; color:white; border:none; border-radius:4px; padding:2px 8px;">מחק</button>
                  </div>`).join('');
          }
          function setToken(t) { activeToken = t; localStorage.setItem('activeToken', t); location.reload(); }
          function addToken() { const val = document.getElementById('new-token').value; if(val) { let tk = JSON.parse(localStorage.getItem('myTokens')||'[]'); tk.push(val); localStorage.setItem('myTokens', JSON.stringify(tk)); renderTokens(); } }
          function deleteToken(t) { let tk = JSON.parse(localStorage.getItem('myTokens')||'[]'); tk = tk.filter(x => x!==t); localStorage.setItem('myTokens', JSON.stringify(tk)); renderTokens(); }
      
          // הרצה ראשונית
          checkAccess();
          renderTokens();
      </script>
      </body>
      </html>
      

      קרדיט: @אA


      קובץ לפתיחת שלוחת טרוויה

      הקוד

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מנהל טריוויה IVR2 - שליטה מלאה</title>
          <link href="https://fonts.googleapis.com/css2?family=Assistant:wght@400;700&family=Courier+Prime&display=swap" rel="stylesheet">
          <style>
              :root { --primary: #1a73e8; --bg: #f8f9fa; --border: #dadce0; --log-bg: #1e1e1e; }
              body { font-family: 'Assistant', sans-serif; background: var(--bg); margin: 0; padding: 10px; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
              .app-wrapper { max-width: 1000px; margin: auto; background: white; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); display: flex; flex-direction: column; max-height: 95vh; width: 100%; }
              .header { padding: 15px 25px; border-bottom: 2px solid #eee; display: flex; justify-content: space-between; align-items: center; }
              .scroll-area { padding: 20px; overflow-y: auto; flex-grow: 1; }
              .grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; }
              .full { grid-column: span 3; }
              .half { grid-column: span 1.5; }
              .field { display: flex; flex-direction: column; }
              label { font-weight: 700; font-size: 12px; margin-bottom: 4px; color: #555; }
              input, select { padding: 8px; border: 1px solid var(--border); border-radius: 6px; font-size: 13px; background: #fff; }
              .btn { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; }
              .btn-primary { background: var(--primary); color: white; }
              .btn-success { background: #1e8e3e; color: white; width: 100%; margin-top: 15px; }
              #log-area { background: var(--log-bg); color: #00ff00; font-family: 'Courier Prime', monospace; padding: 12px; height: 120px; overflow-y: auto; font-size: 12px; border-top: 4px solid #333; direction: ltr; text-align: left; }
              .modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 1000; justify-content: center; align-items: center; }
              .modal-content { background: white; width: 90%; max-width: 800px; border-radius: 12px; padding: 25px; overflow-y: auto; max-height: 90vh; }
              .section-title { grid-column: span 3; margin-top: 15px; padding: 6px 12px; background: #e8f0fe; color: #1a73e8; font-weight: bold; border-radius: 6px; font-size: 13px; border-right: 4px solid var(--primary); }
              .hidden { display: none; }
          </style>
      </head>
      <body>
      
      <div class="app-wrapper">
          <div class="header">
              <h2 style="margin:0; font-size: 1.2rem;">מנהל טריוויה IVR2 - הגדרות מלאות</h2>
              <button id="add-btn" class="btn btn-primary hidden" onclick="openModal()">+ הוספת שאלה</button>
          </div>
      
          <div id="login-screen" class="scroll-area">
              <div class="grid" style="grid-template-columns: 1fr 1fr;">
                  <div class="field"><label>מספר מערכת</label><input type="text" id="user"></div>
                  <div class="field"><label>סיסמה</label><input type="password" id="pass"></div>
                  <div class="field full"><label>נתיב שלוחה</label><input type="text" id="path" value="/1"></div>
                  <button class="btn btn-primary full" onclick="doLogin()">התחבר</button>
              </div>
          </div>
      
          <div id="manage-screen" class="scroll-area hidden">
              <div class="grid">
                  <div class="section-title">הגדרות ניקוד ונסיונות</div>
                  <div class="field"><label>ניקוד ניסיון 1 (points_1)</label><input type="text" id="trivia_questions_points_1" value="2"></div>
                  <div class="field"><label>ניקוד ניסיון 2 (points_2)</label><input type="text" id="trivia_questions_points_2" value="1"></div>
                  <div class="field"><label>ניסיון אחד בלבד?</label>
                      <select id="trivia_questions_only_once"><option value="no">לא</option><option value="yes">כן (yes)</option></select>
                  </div>
      
                  <div class="section-title">הגדרות השמעה וחשיפה</div>
                  <div class="field"><label>ללא חשיפת תשובה?</label>
                      <select id="trivia_questions_without_reveals_answer"><option value="no">לא</option><option value="yes">כן (yes)</option></select>
                  </div>
                  <div class="field"><label>סדר אקראי?</label>
                      <select id="trivia_questions_random"><option value="no">לא</option><option value="yes">כן (yes)</option></select>
                  </div>
                  <div class="field"><label>דילוג על הסבר?</label>
                      <select id="trivia_questions_no_explanation"><option value="no">לא</option><option value="yes">כן (yes)</option></select>
                  </div>
      
                  <div class="section-title">ניהול זמנים וסיום</div>
                  <div class="field"><label>זמן למענה (שניות)</label><input type="number" id="trivia_questions_time_to_answer" value="15"></div>
                  <div class="field"><label>כמות שאלות למשחק</label><input type="number" id="trivia_questions_total_questions" value="0"></div>
                  <div class="field"><label>סגירת השלוחה בסיום?</label>
                      <select id="trivia_questions_hangup_at_end"><option value="no">לא</option><option value="yes">כן (yes)</option></select>
                  </div>
      
                  <div class="section-title">הגדרות מתקדמות</div>
                  <div class="field"><label>השמעת ניקוד בסיום?</label>
                      <select id="trivia_questions_play_points_at_end"><option value="yes">כן</option><option value="no">לא</option></select>
                  </div>
                  <div class="field"><label>מניעת כפל משחקים?</label>
                      <select id="trivia_questions_once_a_day"><option value="no">לא</option><option value="yes">כן (פעם ביום)</option></select>
                  </div>
                  <div class="field"><label>הקלטת שם מאזין?</label>
                      <select id="trivia_questions_record_name"><option value="no">לא</option><option value="yes">כן</option></select>
                  </div>
              </div>
          </div>
      
          <div id="log-area"><div style="color:#aaa">> המערכת מוכנה לסנכרון.</div></div>
      </div>
      
      <div id="qModal" class="modal">
          <div class="modal-content">
              <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; border-bottom:1px solid #eee; padding-bottom:10px;">
                  <h3 style="margin:0">הוספת שאלה (תיקייה)</h3>
                  <button class="btn" style="background:#eee" onclick="closeModal()">X</button>
              </div>
             
              <div class="grid" style="grid-template-columns: 1fr 1fr;">
                  <div class="field full"><label>מספר תיקייה</label><input type="text" id="q_folder" placeholder="001"></div>
                  <div class="field full"><label>סוג השאלה</label>
                      <select id="ui_mode" onchange="updateUI()">
                          <option value="american">אמריקאי (Q, A, B, C, D)</option>
                          <option value="yes_no">נכון / לא נכון (q_yes / q_no)</option>
                      </select>
                  </div>
      
                  <div id="box-american" class="full grid" style="grid-template-columns: 1fr 1fr;">
                      <div class="field full"><label>שאלה (Q.wav)</label><input type="file" id="f_q"></div>
                      <div class="field"><label>נכונה (A.wav)</label><input type="file" id="f_a"></div>
                      <div class="field"><label>שגויה B</label><input type="file" id="f_b"></div>
                      <div class="field"><label>שגויה C</label><input type="file" id="f_c"></div>
                      <div class="field"><label>שגויה D</label><input type="file" id="f_d"></div>
                  </div>
      
                  <div id="box-yesno" class="full hidden grid" style="grid-template-columns: 1fr;">
                      <div class="field"><label>קובץ הקלטה</label><input type="file" id="f_yn"></div>
                      <div class="field"><label>התשובה הנכונה</label>
                          <select id="yn_val">
                              <option value="q_yes">נכון (1)</option>
                              <option value="q_no">לא נכון (0)</option>
                          </select>
                      </div>
                  </div>
                 
                  <button class="btn btn-success full" onclick="startUpload()">בצע סנכרון שלוחה ושאלה</button>
              </div>
          </div>
      </div>
      
      <script>
          let token = '';
          const API = 'https://www.call2all.co.il/ym/api/';
      
          function addLog(m, e=false) {
              const div = document.createElement('div');
              if(e) div.style.color = '#ff5555';
              div.innerText = `[${new Date().toLocaleTimeString()}] ${m}`;
              document.getElementById('log-area').appendChild(div);
              document.getElementById('log-area').scrollTop = document.getElementById('log-area').scrollHeight;
          }
      
          async function doLogin() {
              const u = document.getElementById('user').value, p = document.getElementById('pass').value;
              try {
                  const r = await fetch(`${API}Login?username=${u}&password=${p}`);
                  const d = await r.json();
                  if (d.responseStatus === 'OK') {
                      token = d.token;
                      addLog("חיבור הצליח.");
                      document.getElementById('login-screen').classList.add('hidden');
                      document.getElementById('manage-screen').classList.remove('hidden');
                      document.getElementById('add-btn').classList.remove('hidden');
                  } else { addLog(d.message, true); }
              } catch (e) { addLog("שגיאת תקשורת/CORS", true); }
          }
      
          function updateUI() {
              const mode = document.getElementById('ui_mode').value;
              document.getElementById('box-american').className = (mode === 'american' ? 'full grid' : 'hidden');
              document.getElementById('box-yesno').className = (mode === 'yes_no' ? 'full grid' : 'hidden');
          }
      
          async function startUpload() {
              const folder = document.getElementById('q_folder').value, root = document.getElementById('path').value, mode = document.getElementById('ui_mode').value;
              if(!folder) return alert("הזן תיקייה");
      
              addLog(`בונה ext.ini עם כל ההגדרות...`);
      
              // יצירת קובץ ה-ext.ini המלא עם כל מה שמופיע ב-Dashboard
              let ini = `type=trivia_questions\n`;
              const settings = [
                  'trivia_questions_points_1', 'trivia_questions_points_2', 'trivia_questions_only_once',
                  'trivia_questions_without_reveals_answer', 'trivia_questions_random', 'trivia_questions_no_explanation',
                  'trivia_questions_time_to_answer', 'trivia_questions_total_questions', 'trivia_questions_hangup_at_end',
                  'trivia_questions_play_points_at_end', 'trivia_questions_once_a_day', 'trivia_questions_record_name'
              ];
      
              settings.forEach(s => {
                  const val = document.getElementById(s).value;
                  if(val !== 'no' && val !== '0') ini += `${s}=${val}\n`;
                  else if(val === 'no' || val === '0') { /* אפשר להשאיר ריק או לכתוב no לפי הצורך */ }
              });
      
              await uploadFile(new Blob([ini], {type:'text/plain'}), root + "/ext.ini");
      
              // העלאת קבצים
              if(mode === 'american') {
                  const map = {'Q.wav': 'f_q', 'A.wav': 'f_a', 'B.wav': 'f_b', 'C.wav': 'f_c', 'D.wav': 'f_d'};
                  for(let [file, id] of Object.entries(map)) {
                      const b = document.getElementById(id).files[0];
                      if(b) await uploadFile(b, `${root}/${folder}/${file}`);
                  }
              } else {
                  const b = document.getElementById('f_yn').files[0];
                  if(b) await uploadFile(b, `${root}/${folder}/${document.getElementById('yn_val').value}.wav`);
              }
              addLog(`סיום פעולה בתיקייה ${folder}.`);
              closeModal();
          }
      
          async function uploadFile(blob, rawPath) {
              const path = 'ivr2:' + (rawPath.startsWith('/') ? rawPath : '/' + rawPath);
              const fd = new FormData();
              fd.append('token', token); fd.append('path', path); fd.append('file', blob, path.split('/').pop());
              try {
                  const r = await fetch(`${API}UploadFile`, {method: 'POST', body: fd});
                  const d = await r.json();
                  if(d.responseStatus === 'OK') addLog(`הועלה בהצלחה: ${path}`);
              } catch(e) { addLog("שגיאת העלאה", true); }
          }
      
          function openModal() { document.getElementById('qModal').style.display = 'flex'; updateUI(); }
          function closeModal() { document.getElementById('qModal').style.display = 'none'; }
      </script>
      </body>
      </html>
      

      קרדיט:@אa

      פורסם בהסברים מסודרים ממשתמשים
      א
      אA
    • קובץ להעלאת שלוחות וקבצים למערכת מתוך תקייה במחשב

      יצרתי קובץ html דרכו אפשר להעלות למערכת תקייה עם תקיות וקבצים הבנויים בצורת עץ תקיות, והשלוחות יווצרו אוטומטית וכן הקבצים יעלו לשלוחות.

      אני פותח לזה פוסט נפרד כי זה דבר שביקשו כאן כבר כמה פעמים, וכדי שיוכלו למצוא אותו בקלות.

      הוראות שימוש:

      1. יש לפתוח תקייה חדשה שהיא תהיה השלוחה הראשית, בתוכה יש לפתוח תקיות שכל תקיה היא שלוחה ותוכן ניתן לפתוח עוד תקיות.
        בתקייה הראשית ברירת מחדל נוצרת שלוחת תפריט אלא אם כן הוגדר אחרת, כמו כן כל תקייה ריקה ברירת מחדל היא תקיית השמעת קבצים.
      2. כדי להגדיר שלוחה, יש להכניס קובץ טקסט בשם ext (אין צורך בסיומת), ובתוכו להכניס את ההגדרות הרצויות.
        קבצי שמע עולים כמו שהם כולל השם לשלוחות.
      3. קבצי טקסט, יש להכניס קובץ טקסט לתקייה ושהסיומת תהיה tts, הקובץ יעלה לשלוחה ללא הסיומת הזו (דוגמא קובץ 000.tts, יעלה בתור 000).

      הקוד

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>מעלה תיקיות ומבנה שלוחות - ימות המשיח</title>
          <style>
              :root {
                  --primary: #3498db;
                  --success: #2ecc71;
                  --danger: #e74c3c;
                  --bg: #f4f7f6;
                  --dark: #2c3e50;
              }
              body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background-color: var(--bg); color: var(--dark); text-align: right; }
              .wrapper { max-width: 900px; margin: 40px auto; padding: 0 20px; position: relative; }
              
              .reset-btn {
                  position: absolute;
                  top: -10px;
                  left: 20px;
                  background: var(--danger);
                  color: white;
                  border: none;
                  padding: 8px 15px;
                  border-radius: 5px;
                  cursor: pointer;
                  font-size: 14px;
                  font-weight: bold;
                  transition: all 0.3s;
                  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
              }
              .reset-btn:hover { opacity: 0.8; transform: translateY(-1px); }
       
              .card { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); margin-bottom: 25px; border: 1px solid #eee; }
              h2 { margin-top: 0; color: var(--dark); border-bottom: 3px solid var(--primary); display: inline-block; padding-bottom: 10px; }
              
              .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
              .input-group { display: flex; flex-direction: column; gap: 8px; }
              label { font-weight: bold; font-size: 14px; }
              input { padding: 12px; border: 2px solid #eee; border-radius: 8px; font-size: 16px; transition: border 0.3s; }
              input:focus { border-color: var(--primary); outline: none; }
              
              .full-width { grid-column: 1 / -1; }
              
              .btn-main { 
                  background: var(--primary); color: white; border: none; padding: 15px; 
                  border-radius: 8px; cursor: pointer; font-size: 18px; font-weight: bold; 
                  margin-top: 10px; width: 100%; transition: all 0.3s; 
                  box-shadow: 0 4px 6px rgba(52, 152, 219, 0.2);
              }
              .btn-main:hover { background: #2980b9; transform: translateY(-1px); }
              .btn-main:disabled { background: #bdc3c7; cursor: not-allowed; transform: none; }
       
              /* אזור התקדמות משודרג */
              .progress-wrapper { margin-top: 25px; display: none; padding: 15px; background: #fafafa; border-radius: 10px; border: 1px solid #eee; }
              .progress-container { 
                  width: 100%; 
                  background: #e0e0e0; 
                  height: 35px; 
                  border-radius: 20px; 
                  overflow: hidden; 
                  position: relative; 
                  box-shadow: inset 0 2px 5px rgba(0,0,0,0.1);
                  border: 1px solid #ccc;
              }
              .progress-bar { 
                  height: 100%; 
                  width: 0%; 
                  background: linear-gradient(45deg, #2ecc71 25%, #27ae60 25%, #27ae60 50%, #2ecc71 50%, #2ecc71 75%, #27ae60 75%, #27ae60);
                  background-size: 40px 40px;
                  animation: move-stripes 2s linear infinite;
                  transition: width 0.4s cubic-bezier(0.17, 0.67, 0.83, 0.67);
                  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
              }
              
              @keyframes move-stripes {
                  from { background-position: 40px 0; }
                  to { background-position: 0 0; }
              }
       
              .progress-text { 
                  position: absolute; 
                  width: 100%; 
                  text-align: center; 
                  top: 0; 
                  line-height: 35px; 
                  color: #fff; 
                  font-weight: 900; 
                  text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
                  font-size: 16px;
              }
       
              .log-header { font-weight: bold; margin-bottom: 12px; display: flex; align-items: center; gap: 10px; color: var(--dark); }
              .log-box { 
                  background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; 
                  font-family: 'Consolas', monospace; height: 300px; overflow-y: auto; 
                  font-size: 13px; line-height: 1.6; border: 4px solid #333;
                  box-shadow: inset 0 5px 15px rgba(0,0,0,0.5);
              }
              .log-info { color: #5dade2; }
              .log-success { color: #58d68d; font-weight: bold; }
              .log-error { color: #ec7063; }
              .log-warn { color: #f4d03f; }
              .log-tts { color: #bb8fce; }
          </style>
      </head>
      <body>
       
      <div class="wrapper">
          <button class="reset-btn" onclick="resetAll()">✕ איפוס תהליך</button>
          
          <div class="card">
              <h2>העלאת מבנה תיקיות לימות המשיח</h2>
              
              <div class="grid">
                  <div class="input-group">
                      <label>טוקן מערכת:</label>
                      <input type="text" id="token" placeholder="הכנס טוקן כאן...">
                  </div>
                  <div class="input-group">
                      <label>שלוחת יעד ראשית:</label>
                      <input type="text" id="targetPath" placeholder="לדוגמה: 1">
                  </div>
                  <div class="input-group full-width">
                      <label>בחר תיקייה מהמחשב:</label>
                      <input type="file" id="folderInput" webkitdirectory>
                  </div>
                  <div class="full-width">
                      <button id="startBtn" class="btn-main" onclick="processUpload()">התחל העלאה למערכת</button>
                  </div>
              </div>
       
              <div class="progress-wrapper" id="progBox">
                  <div class="log-header">🔄 סטטוס התקדמות:</div>
                  <div class="progress-container">
                      <div class="progress-bar" id="progBar"></div>
                      <div class="progress-text" id="progText">0%</div>
                  </div>
              </div>
          </div>
       
          <div class="card">
              <div class="log-header">📜 תיעוד פעולות (Log):</div>
              <div class="log-box" id="logBox">ממתין לתחילת עבודה...</div>
          </div>
      </div>
       
      <script>
          const sleep = ms => new Promise(res => setTimeout(res, ms));
       
          function addLog(msg, type = "info") {
              const logBox = document.getElementById('logBox');
              let className = "log-info";
              if (type === "success") className = "log-success";
              if (type === "error") className = "log-error";
              if (type === "warn") className = "log-warn";
              if (type === "tts") className = "log-tts";
       
              logBox.innerHTML += `<div class="${className}">> ${msg}</div>`;
              logBox.scrollTop = logBox.scrollHeight;
          }
       
          function resetAll() {
              if(!confirm("האם אתה בטוח שברצונך לאפס את כל הנתונים?")) return;
              document.getElementById('token').value = "";
              document.getElementById('targetPath').value = "";
              document.getElementById('folderInput').value = "";
              document.getElementById('logBox').innerHTML = "המערכת אופסה. ממתין לפעולה...";
              document.getElementById('progBox').style.display = "none";
              document.getElementById('progBar').style.width = "0%";
              document.getElementById('progText').innerText = "0%";
              document.getElementById('startBtn').disabled = false;
              addLog("בוצע איפוס נתונים.");
          }
       
          async function processUpload() {
              const token = document.getElementById('token').value;
              const targetPath = document.getElementById('targetPath').value;
              const fileList = document.getElementById('folderInput').files;
              
              if (!token || !fileList.length) {
                  alert("חובה למלא טוקן ולבחור תיקייה");
                  return;
              }
       
              document.getElementById('startBtn').disabled = true;
              document.getElementById('progBox').style.display = 'block';
              const progBar = document.getElementById('progBar');
              const progText = document.getElementById('progText');
              
              document.getElementById('logBox').innerHTML = "";
              addLog("מתחיל בניית מבנה נתונים...", "info");
       
              const folderStructure = {};
              for (let file of fileList) {
                  const parts = file.webkitRelativePath.split('/');
                  parts.shift(); 
                  const fileName = parts.pop();
                  const folderPath = parts.join('/');
                  if (!folderStructure[folderPath]) folderStructure[folderPath] = [];
                  folderStructure[folderPath].push({ name: fileName, file: file });
              }
       
              const sortedPaths = Object.keys(folderStructure).sort((a, b) => {
                  const depthA = a === "" ? 0 : a.split('/').length;
                  const depthB = b === "" ? 0 : b.split('/').length;
                  return depthA - depthB;
              });
       
              for (let i = 0; i < sortedPaths.length; i++) {
                  const folderPath = sortedPaths[i];
                  const currentIvrPath = folderPath ? `${targetPath}/${folderPath}` : targetPath;
                  const folderFiles = folderStructure[folderPath];
                  const isRoot = (folderPath === "");
       
                  addLog(`מעבד שלוחה: ${currentIvrPath}`, "warn");
       
                  const extFileObj = folderFiles.find(f => f.name.toLowerCase().split('.')[0] === 'ext');
                  let extContent = "";
                  if (extFileObj) {
                      extContent = await extFileObj.file.text();
                  } else {
                      extContent = isRoot ? "type=menu" : "type=playfile";
                  }
                  
                  const extBlob = new Blob([extContent], { type: 'text/plain' });
                  await uploadFile(token, `${currentIvrPath}/ext.ini`, extBlob);
                  await sleep(400);
       
                  for (let fObj of folderFiles) {
                      const fileNameLower = fObj.name.toLowerCase();
                      if (fileNameLower.split('.')[0] === 'ext') continue;
       
                      const isAudio = /\.(wav|mp3|ogg|wma)$/i.test(fileNameLower);
                      
                      let destName = "";
                      let blobToSend;
       
                      if (isAudio) {
                          destName = fObj.name.replace(/\.[^/.]+$/, ".wav");
                          blobToSend = fObj.file;
                          addLog(`מעלה שמע: ${destName}`, "info");
                      } else if (fileNameLower.includes('.tts')) {
                          let cleanName = fObj.name.replace(/\.tts/gi, ""); 
                          cleanName = cleanName.replace(/\.[^/.]+$/, ""); 
                          destName = cleanName + ".tts";
                          
                          const text = await fObj.file.text();
                          blobToSend = new Blob([text], { type: 'text/plain' });
                          addLog(`מעלה קובץ TTS: ${destName}`, "tts");
                      } else {
                          destName = fObj.name.replace(/\.[^/.]+$/, "") + ".ini";
                          const text = await fObj.file.text();
                          blobToSend = new Blob([text], { type: 'text/plain' });
                          addLog(`מעלה הגדרות: ${destName}`, "info");
                      }
                      
                      await uploadFile(token, `${currentIvrPath}/${destName}`, blobToSend);
                  }
       
                  const pct = Math.round(((i + 1) / sortedPaths.length) * 100);
                  progBar.style.width = pct + '%';
                  progText.innerText = pct + '%';
              }
       
              addLog("סיום מוצלח! כל הקבצים הועלו.", "success");
              document.getElementById('startBtn').disabled = false;
          }
       
          async function uploadFile(token, fullPath, blob) {
              const fd = new FormData();
              fd.append('token', token);
              fd.append('path', `ivr2:${fullPath}`);
              fd.append('qqfile', blob, fullPath.split('/').pop()); 
              try {
                  const res = await fetch(`https://www.call2all.co.il/ym/api/UploadFile`, {
                      method: 'POST',
                      body: fd
                  });
                  const data = await res.json();
                  if(data.responseStatus !== "OK") addLog(`שגיאה ב-${fullPath}: ${data.message}`, "error");
                  return data;
              } catch (e) {
                  addLog(`כישלון תקשורת ב-${fullPath}`, "error");
              }
          }
      </script>
      </body>
      </html>
      
      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • RE: "ביכולתך להצביע בעד משתמש מסוים רק 6 פעמים ביום"

      רק מזכיר

      ג. הפורום אינו רשת חברתית ומשכך אינו מיועד לבקשות של לייקים, שימוש במנוע הלייקים שלא לצורכו ופתיחת משתמשים כפולים לצורך זה. כפשוט שאין לכתוב "תתנו לי לייק" וכדומה.

      ובעיקר

      מטרת הלייקים היא לתת הערכה על משתמש מה רמת הידע שלו ולתת למשתמש שמשקיע הרגשה טובה.

      בעצם עיקר הענין של הלייקים הוא כדי לסמן על הבנה של ענין או הערכה לידע שהביא המשתמש, וכן לתת הרגשה טובה.

      פורסם בעל הפורום
      א
      אA

    פוסטים אחרונים שנוצרו על ידי אA

    • RE: איך אני פותר את זה?

      @BEN-ZION
      מעניין.
      לי זה עדיין כך

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • RE: פתיחת שלוחה אישית למאזין

      @אחד-מהציבור
      שלוחת API של עידכון שלוחה, ושיעבור לפני כן בהוספת ערך שיוסיף את הid שלו לנתיב.
      כדי שזה יעבוד תצטרך להשתמש בסקריפט של יהודה צ.כ.
      לחלופין יתכן וזה גם יעבוד בפתיחת שלוחות לפי זיהוי אישי וכדו'.

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • RE: הוספת שמות לדוחות - לא נקלט

      @BEN-ZION
      בדיוק כך.
      זה בחיים לא קרה לי.
      והשתמשתי המון באפשרות של הוספת ערך

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • RE: תגובות פאנל ניהול מותאם אישית

      @BEN-ZION

      1. אין צורך בשרת מספיק סקירפט והוא שולף את הטוקן משם.
      2. תתפלא לשמוע שזה ממש פשוט.
        זה מוריד את הקובץ לדפדפן ולא למחשב ומפעיל אותו משם.
      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • RE: הוספת שמות לדוחות - לא נקלט

      @BEN-ZION
      אני מדבר בדיוק ע"ז.
      ר הבעיה היא שהמערכת לא קולטת את השמות שבקובץ.

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • RE: תגובות פאנל ניהול מותאם אישית

      @BEN-ZION
      לדעתי תעשה את ההגבלה כמו שעשו כאן כבר, שהמנהל מגדיר את ההגבלה.
      אפשר להגדיר גם שהמנהל יוצר קישור מיוחד לכל מי שהוא רוצה לתת לו ניהול מוגבל שהקישור עצמו מוגבל.(זה לא טוקן)

      פורסם בעזרה הדדית למשתמשים מתקדמים
      א
      אA
    • RE: הוספת שמות לדוחות - לא נקלט

      @לימוד-בתורת-מרן
      כדי להוסף לשם שמות מכניסים אותם שם לא במערכת עצמה.
      אבל תעשה טובה, ותפתח נושא נפרד.

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • RE: תגובות כל כלי הקריינות וה-AI (והטקסט לדיבור) במקום אחד

      @1668
      אכן פתוח.
      זה שיבוט?

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • הוספת שמות לדוחות - לא נקלט

      רציתי להוסיף לדוחות במערכת שמות.
      אז פתחתי את הקובץ המתאים בשלוחה, עברתי לאקסל שבו היום השמות והזיהויים העתקתי הכל והדבקתי בקובץ.
      נכנסתי לשיחות נכנסות, ו... כלום.
      נכנסתי שוב לקובץ הכנסתי ידנית מספר נוסף ושם, חזרתי לשיחות ו...נקלט.
      ניסיתי להדביק במצב עברית, אנגלית, ניסיתי להעלות קובץ מהמחשב וכלום!!!
      דבר נוסף: במערכת צוברים נקודות, ורציתי שתיהיה צבירה גם לפי קבוצות.
      פתחתי את הקובץ המתאים, הכנסתי את הזיהויים והקבוצות, חיכיתי מעט, נכנסתי ללוג ו... כלום.
      חזרתי לקובץ שלי, ואני מגלה שבכל קבוצה השם המאזין הראשון בסדר, וכל השאר עם שגיאה בהתחלה.
      גם כאן ניסיתי את כל האפשרויות ולא עזר כלום.
      אציין שאלו מספרי ת.ז. שכמעט כולם מתחילים ב22 וכו'.
      מישהו יודע מה ניתן לעשות או שזה באג חדש?!
      @פלוס מומחה הניקוד, יש לך פיתרון לבאג השני?
      ואם כבר אני מתייג אותך, יש לי בעיה נוספת.
      הלוג הפסיק לצבור לקבוצו.הבעיה התחילה אחרי שנגעתי בלוג שלהם, ואז התחיל הבלאגן גם אחרי שמחקתי את הקבצים הקודמים.

      פורסם בשאלות ועזרה הדדית
      א
      אA
    • RE: ווטסאפון

      @sumone
      אם היא מערכת ישנה, א"א.

      פורסם בשאלות ועזרה הדדית
      א
      אA