• דף הבית
    • אינדקס קישורים
    • פוסטים אחרונים
    • משתמשים
    • חיפוש בהגדרות המתקדמות
    • חיפוש גוגל בפורום
    • ניהול המערכת
    • ניהול המערכת - שרת private
    • הרשמה
    • התחברות

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

    מתוזמן נעוץ נעול הועבר טיפים עצות והדגמות מהמשתמשים
    44 פוסטים 7 כותבים 689 צפיות 4 עוקבים
    טוען פוסטים נוספים
    • מהישן לחדש
    • מהחדש לישן
    • הכי הרבה הצבעות
    תגובה
    • תגובה כנושא
    התחברו כדי לפרסם תגובה
    נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.
    • ה מחובר
      הדיבל המעופף @איל משולש
      נערך לאחרונה על ידי

      @איל-משולש
      איך וכיצד שמים את הפרמטרים לשליחה? והאם המודל עדיין פעיל?

      א תגובה 1 תגובה אחרונה תגובה ציטוט 0
      • א מנותק
        איל משולש @הדיבל המעופף
        נערך לאחרונה על ידי

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

        ה תגובה 1 תגובה אחרונה תגובה ציטוט 0
        • ה מחובר
          הדיבל המעופף @איל משולש
          נערך לאחרונה על ידי

          @איל-משולש כתב:

          כל הפרמטרים ישלחו לכל המודלים, אבל זה לא ישפיע עליהם)

          אתה בטוח?

          י א 2 תגובות תגובה אחרונה תגובה ציטוט 0
          • י מנותק
            יצחק י. @הדיבל המעופף
            נערך לאחרונה על ידי יצחק י.

            @הדיבל-המעופף
            תנסה את זה (תפתח ב-HTML)

            <!DOCTYPE html>
            <html lang="he" dir="rtl">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>מחולל ניתוב מקשים ל-API - ימות המשיח</title>
                <style>
                    body {
                        font-family: system-ui, -apple-system, sans-serif;
                        background-color: #f8fafc;
                        color: #1e293b;
                        margin: 0;
                        padding: 20px;
                    }
                    header {
                        text-align: center;
                        margin-bottom: 25px;
                    }
                    header h1 {
                        color: #0284c7;
                        margin-bottom: 5px;
                    }
                    .container {
                        display: flex;
                        gap: 25px;
                        max-width: 1200px;
                        margin: 0 auto;
                    }
                    .form-panel {
                        flex: 1;
                        background: #ffffff;
                        padding: 25px;
                        border-radius: 12px;
                        box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
                    }
                    .preview-panel {
                        width: 420px;
                        background: #1e293b;
                        color: #e2e8f0;
                        padding: 25px;
                        border-radius: 12px;
                        display: flex;
                        flex-direction: column;
                        position: sticky;
                        top: 20px;
                        height: fit-content;
                    }
                    fieldset {
                        border: 1px solid #cbd5e1;
                        border-radius: 10px;
                        padding: 20px;
                        margin-bottom: 30px;
                        background: #fafafa;
                        position: relative;
                    }
                    legend {
                        font-weight: bold;
                        color: #0284c7;
                        padding: 0 8px;
                        font-size: 1.1rem;
                    }
                    .form-group {
                        margin-bottom: 18px;
                    }
                    label {
                        display: block;
                        margin-bottom: 6px;
                        font-size: 0.92rem;
                        font-weight: 600;
                        color: #334155;
                    }
                    input[type="text"], select {
                        width: 100%;
                        padding: 10px 12px;
                        border: 1px solid #cbd5e1;
                        border-radius: 6px;
                        box-sizing: border-box;
                        direction: ltr;
                        text-align: left;
                        font-size: 0.95rem;
                        background: #fff;
                    }
                    select {
                        direction: rtl;
                        text-align: right;
                    }
                    input[type="text"]:focus, select:focus {
                        outline: none;
                        border-color: #0284c7;
                        box-shadow: 0 0 0 3px rgba(2, 132, 199, 0.15);
                    }
                    .param-row {
                        display: flex;
                        gap: 8px;
                        margin-bottom: 10px;
                        align-items: center;
                    }
                    .code-box {
                        background: #0f172a;
                        color: #38bdf8;
                        padding: 15px;
                        border-radius: 6px;
                        font-family: monospace;
                        white-space: pre-wrap;
                        direction: ltr;
                        text-align: left;
                        font-size: 0.85rem;
                        height: 420px;
                        overflow-y: auto;
                        line-height: 1.5;
                    }
                    .btn {
                        padding: 8px 14px;
                        border: none;
                        border-radius: 6px;
                        font-weight: bold;
                        cursor: pointer;
                        font-size: 0.85rem;
                        transition: background-color 0.2s;
                    }
                    .btn-add-key {
                        background-color: #10b981;
                        color: white;
                        width: 100%;
                        padding: 12px;
                        font-size: 1rem;
                        margin-top: 10px;
                    }
                    .btn-add-key:hover { background-color: #059669; }
            
                    .btn-add-param {
                        background-color: #0ea5e9;
                        color: white;
                        margin-top: 5px;
                    }
                    .btn-add-param:hover { background-color: #0284c7; }
            
                    .btn-remove {
                        background-color: #ef4444;
                        color: white;
                        position: absolute;
                        top: 15px;
                        left: 15px;
                        padding: 5px 10px;
                        font-size: 0.8rem;
                    }
                    .btn-remove:hover { background-color: #dc2626; }
            
                    .btn-remove-param {
                        background-color: #fee2e2;
                        color: #991b1b;
                        padding: 10px 12px;
                    }
                    .btn-remove-param:hover { background-color: #fecaca; }
            
                    .copy-btn {
                        background-color: #38bdf8;
                        color: #0f172a;
                        border: none;
                        padding: 12px;
                        border-radius: 6px;
                        font-weight: bold;
                        cursor: pointer;
                        margin-top: 15px;
                        font-size: 0.95rem;
                        transition: background-color 0.2s;
                    }
                    .copy-btn:hover { background-color: #7dd3fc; }
                </style>
            </head>
            <body>
            
                <header>
                    <h1>מחולל ניתוב מקשים ל-API (שיטת הגישור)</h1>
                    <p>ימות המשיח</p>
                </header>
            
                <div class="container">
                    <!-- פאנל הזנה -->
                    <div class="form-panel" id="formPanel">
                        <div id="keysContainer">
                            <!-- המקשים יתווספו לכאן דינמית -->
                        </div>
                        <button type="button" class="btn btn-add-key" onclick="addKeyBox()">+ הוסף מקש חדש</button>
                    </div>
            
                    <!-- פאנל תצוגה מקדימה -->
                    <div class="preview-panel">
                        <h3 style="margin-top:0; color:#38bdf8; border-bottom:1px solid #334155; padding-bottom:10px;">תצוגה מקדימה לקובץ INI</h3>
                        <div id="output" class="code-box"></div>
                        <button class="copy-btn" onclick="copyText()">העתק קוד ללוח</button>
                    </div>
                </div>
            
                <script>
                    const initialKeys = [
                        {
                            num: "9",
                            timing: "during",
                            url: "https://nituv-u1oq.onrender.com//api/process",
                            params: ["A=0799999999:1234"]
                        },
                        {
                            num: "*",
                            timing: "more",
                            url: "https://pirtey-vashmaha.onrender.com/",
                            params: ["token=0799999999:1234", "password=3312"]
                        }
                    ];
            
                    function renderInitial() {
                        const container = document.getElementById('keysContainer');
                        container.innerHTML = '';
                        initialKeys.forEach(k => addKeyBox(k.num, k.timing, k.url, k.params));
                    }
            
                    const validKeys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "*", "#"];
            
                    function getKeysOptionsHtml(selectedVal) {
                        let html = '';
                        validKeys.forEach(k => {
                            let sel = (k === selectedVal) ? 'selected' : '';
                            html += `<option value="${k}" ${sel}>מקש ${k}</option>`;
                        });
                        return html;
                    }
            
                    function addKeyBox(keyNum = "9", keyTiming = "during", keyUrl = "", paramsList = [""]) {
                        const container = document.getElementById('keysContainer');
                        const fieldset = document.createElement('fieldset');
                        
                        let paramsHtml = '';
                        paramsList.forEach(p => {
                            paramsHtml += `
                                <div class="param-row">
                                    <input type="text" class="param-input" value="${p}" oninput="updateCode()" placeholder="שם=ערך (לדוגמה: token=...)">
                                    <button type="button" class="btn btn-remove-param" onclick="removeParamRow(this)">✕</button>
                                </div>
                            `;
                        });
            
                        fieldset.innerHTML = `
                            <button type="button" class="btn btn-remove" onclick="removeKeyBox(this)">הסר מקש</button>
                            <legend>הגדרת מקש</legend>
                            <div class="form-group">
                                <label>בחר מקש:</label>
                                <select class="key-num" onchange="updateCode()">
                                    ${getKeysOptionsHtml(keyNum)}
                                </select>
                            </div>
                            <div class="form-group">
                                <label>תזמון הקשה (קידומת):</label>
                                <select class="key-timing" onchange="updateCode()">
                                    <option value="during" ${keyTiming === 'during' ? 'selected' : ''}>בעת שמיעת כל קובץ בשלוחה (control_playX)</option>
                                    <option value="more" ${keyTiming === 'more' ? 'selected' : ''}>בתפריט אפשרויות נוספות (control_play_moreAX)</option>
                                    <option value="after" ${keyTiming === 'after' ? 'selected' : ''}>בתפריט שלאחר ההשמעה (control_after_play_moreAX)</option>
                                    <option value="replies" ${keyTiming === 'replies' ? 'selected' : ''}>בעת שמיעת תגובות (control_play_repliesX)</option>
                                    <option value="playlist" ${keyTiming === 'playlist' ? 'selected' : ''}>בעת שמיעת רשימת השמעה (control_playlistX)</option>
                                </select>
                            </div>
                            <div class="form-group">
                                <label>כתובת לשליחה (כמו api_link בשלוחה רגילה):</label>
                                <input type="text" class="key-url" value="${keyUrl}" oninput="updateCode()">
                            </div>
                            <div class="form-group">
                                <label>פרמטרים לשלוחה (api_add_0, api_add_1 וכו'):</label>
                                <div class="params-container">
                                    ${paramsHtml}
                                </div>
                                <button type="button" class="btn btn-add-param" onclick="addParamRow(this)">+ הוסף שורת פרמטר</button>
                            </div>
                        `;
                        container.appendChild(fieldset);
                        updateCode();
                    }
            
                    function removeKeyBox(btn) {
                        btn.closest('fieldset').remove();
                        updateCode();
                    }
            
                    function addParamRow(btn, val = "") {
                        const paramsContainer = btn.previousElementSibling;
                        const row = document.createElement('div');
                        row.className = 'param-row';
                        row.innerHTML = `
                            <input type="text" class="param-input" value="${val}" oninput="updateCode()" placeholder="שם=ערך">
                            <button type="button" class="btn btn-remove-param" onclick="removeParamRow(this)">✕</button>
                        `;
                        paramsContainer.appendChild(row);
                        updateCode();
                    }
            
                    function removeParamRow(btn) {
                        const container = btn.closest('.params-container');
                        if (container.querySelectorAll('.param-row').length > 1) {
                            btn.closest('.param-row').remove();
                            updateCode();
                        } else {
                            btn.closest('.param-row').querySelector('input').value = '';
                            updateCode();
                        }
                    }
            
                    function updateCode() {
                        let lines = [];
                        
                        // שרת הגישור הקבוע מראש ברקע
                        lines.push(`api_link=https://gesher.onrender.com/`);
                        lines.push(`api_add_0=plus=yes`);
            
                        let counter = 1;
                        let controls = [];
            
                        const keyFieldsets = document.querySelectorAll('#keysContainer fieldset');
            
                        keyFieldsets.forEach(fieldset => {
                            let kNum = fieldset.querySelector('.key-num').value;
                            let kTiming = fieldset.querySelector('.key-timing').value;
                            let kUrl = fieldset.querySelector('.key-url').value.trim();
                            let paramInputs = fieldset.querySelectorAll('.param-input');
            
                            if (kNum && kUrl) {
                                let fullUrl = kUrl;
                                let paramPairs = [];
                                
                                paramInputs.forEach(input => {
                                    let val = input.value.trim();
                                    if (val) paramPairs.push(val);
                                });
            
                                if (paramPairs.length > 0) {
                                    let separator = fullUrl.includes('?') ? '&' : '?';
                                    fullUrl += separator + paramPairs.join('&');
                                }
            
                                // בניית שם משתנה ה-api_add בהתאם לתווים מיוחדים
                                let formattedKeyForApi = kNum;
                                if (kTiming === 'more' || kTiming === 'after') {
                                    formattedKeyForApi = (kNum === '*') ? 'A' : (kNum === '#') ? 'B' : kNum;
                                }
            
                                lines.push(`api_add_${counter}=PressKey_${formattedKeyForApi}=${fullUrl}`);
            
                                // עיבוד נכון של פקודות הבקרה לפי ימות המשיח ותזמון ההקשה
                                let cleanKey = kNum.replace(/\*/g, 'A').replace(/#/g, 'B');
                                
                                if (kTiming === 'during') {
                                    controls.push(`control_play${cleanKey}=send_api`);
                                } else if (kTiming === 'more') {
                                    controls.push(`control_play_more${cleanKey}=send_api`);
                                } else if (kTiming === 'after') {
                                    // הוספת ההגדרה הנדרשת לתפריט שלאחר השמעה לפי התיעוד
                                    if (!lines.includes('after_play_tfr=tfr_more_options')) {
                                        // נוסיף בהמשך או למעלה
                                    }
                                    controls.push(`control_after_play_more${cleanKey}=send_api`);
                                } else if (kTiming === 'replies') {
                                    controls.push(`control_play_replies${cleanKey}=send_api`);
                                } else if (kTiming === 'playlist') {
                                    controls.push(`control_playlist${cleanKey}=send_api`);
                                }
            
                                counter++;
                            }
                        });
            
                        // אם יש תפריט שלאחר השמעה, נוסיף את ההגדרה הכללית הנדרשת
                        let hasAfterPlay = Array.from(keyFieldsets).some(f => f.querySelector('.key-timing').value === 'after');
                        if (hasAfterPlay) {
                            lines.splice(2, 0, `after_play_tfr=tfr_more_options`);
                        }
            
                        if (controls.length > 0) {
                            lines.push("");
                            controls.forEach(c => lines.push(c));
                        }
            
                        document.getElementById('output').innerText = lines.join('\n');
                    }
            
                    function copyText() {
                        let text = document.getElementById('output').innerText;
                        navigator.clipboard.writeText(text).then(() => {
                            alert('הקוד הועתק בהצלחה!');
                        });
                    }
            
                    renderInitial();
                </script>
            </body>
            </html>
            
            

            לי המודול הזה לא עובד...
            תנסה....

            א תגובה 1 תגובה אחרונה תגובה ציטוט 1
            • א מנותק
              איל משולש @הדיבל המעופף
              נערך לאחרונה על ידי

              @הדיבל-המעופף בטוח שמה?

              תגובה 1 תגובה אחרונה תגובה ציטוט 0
              • א מנותק
                איל משולש @יצחק י.
                נערך לאחרונה על ידי

                @יצחק-י. מה זה?

                י 2 תגובות תגובה אחרונה תגובה ציטוט 0
                • י מנותק
                  יצחק י. @איל משולש
                  נערך לאחרונה על ידי

                  @איל-משולש המודול אמור לעבוד?

                  תגובה 1 תגובה אחרונה תגובה ציטוט 0
                  • י מנותק
                    יצחק י. @איל משולש
                    נערך לאחרונה על ידי יצחק י.

                    @איל-משולש כתב:

                    @יצחק-י. מה זה?

                    עשיתי בגמיני, דף להגדרת המודול

                    ה תגובה 1 תגובה אחרונה תגובה ציטוט 1
                    • ה מחובר
                      הדיבל המעופף @יצחק י.
                      נערך לאחרונה על ידי

                      @יצחק-י. כתב:

                      לי המודול הזה לא עובד...
                      תנסה....

                      יכול להיות זה בגלל שאתה לא עושה שורה נפרדת לכל פרמטר

                      @איל-משולש כתב:

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

                      ושאלתי היא אם אתה בטוח שלא ישבש

                      י תגובה 1 תגובה אחרונה תגובה ציטוט 0
                      • י מנותק
                        יצחק י. @הדיבל המעופף
                        נערך לאחרונה על ידי יצחק י.

                        @הדיבל-המעופף כתב:

                        יכול להיות זה בגלל שאתה לא עושה שורה נפרדת לכל פרמטר

                        לא צריך שורה נפרדת!
                        ככה איל אמר...
                        לך זה עובד?

                        ה תגובה 1 תגובה אחרונה תגובה ציטוט 0
                        • ה מחובר
                          הדיבל המעופף @יצחק י.
                          נערך לאחרונה על ידי הדיבל המעופף

                          @יצחק-י. כתב:

                          ככה איל אמר...

                          לא הוא אומר אלא @ cubase

                          @יצחק-י. כתב:

                          לך זה עובד?

                          אדהכי והכי פיתחתי כבר לבד (קלוד)
                          אתה רוצה את הקוד?

                          י תגובה 1 תגובה אחרונה תגובה ציטוט 0
                          • י מנותק
                            יצחק י. @הדיבל המעופף
                            נערך לאחרונה על ידי

                            @הדיבל-המעופף כתב:

                            אתה רוצה את הקוד?

                            אשמח מאוד

                            ה תגובה 1 תגובה אחרונה תגובה ציטוט 0
                            • י מנותק
                              יצחק י.
                              נערך לאחרונה על ידי

                              @הדיבל-המעופף כתב:

                              נחוץ ביותר להוסיף שיהיה אפשר להגדיר כמה מקשים כשליחת API ולא רק אחד!😔☹️ (כמובן שלכל מקש יהיה כתובת ופרמטרים משלו)

                              לדוגמא שיהיה ככה

                              control_play1=send_api
                              api1_link=https://example.com/YemotApi
                              api1_add_0=foo=111
                              api1_add_1=bar=222
                              

                              ולמשל למקש 5

                              control_play5=send_api
                              api5_link=https://example.com/YemotApi
                              api5_add_0=foo=111
                              api5_add_1=bar=222
                              

                              מצטרף לבקשה...

                              תגובה 1 תגובה אחרונה תגובה ציטוט 0
                              • ה מחובר
                                הדיבל המעופף @יצחק י.
                                נערך לאחרונה על ידי הדיבל המעופף

                                @יצחק-י.
                                זה הקוד לשרת

                                /**
                                 * Cloudflare Worker - ניתוב לפי מקש שנלחץ (send_api ממודול Playfile של ימות המשיח)
                                 * ------------------------------------------------------------------------------------
                                 * הפתרון כאן: כל כתובות היעד מוגדרות ישירות ב-ext.ini של ימות באמצעות api_add,
                                 * ונשלחות אוטומטית עם כל בקשה (יחד עם PressKey - המקש שנלחץ). ה-Worker רק
                                 * קורא את הפרמטר target_<PressKey> מתוך הבקשה עצמה ומעביר אליו את הקריאה.
                                 * אין צורך בקובץ ini נפרד, DownloadFile, או עדכון קוד כשמוסיפים/משנים מקש.
                                 *
                                 * הגדרות בקובץ ext.ini של השלוחה (playfile):
                                 *   type=playfile
                                 *   control_play1=send_api
                                 *   control_play2=send_api
                                 *   control_play_moreA2=send_api            ; מקש *2 (תפריט אפשרויות נוספות)
                                 *   api_link=https://your-worker-subdomain.workers.dev
                                 *   api_url_post=yes
                                 *   api_add_0=target_1=https://server-a.example.com/webhook
                                 *   api_add_1=target_2=https://server-b.example.com/webhook
                                 *   api_add_2=target_star2=https://server-c.example.com/webhook
                                 *   api_add_3=note_1=פנייה למכירות
                                 *   api_add_4=note_2=פנייה לתמיכה
                                 *
                                 * כדי להוסיף מקש 4: מוסיפים control_play4=send_api ועוד שורת api_add
                                 * בפורמט api_add_N=target_4=https://... - וזהו, בלי לגעת בקוד ה-Worker.
                                 *
                                 * מקשים דרך "תפריט אפשרויות נוספות" (אחרי הקשה על *, למשל *2): ימות שולח
                                 * במקרה כזה PressKey בפורמט "מקש-ראשון-מקש-שני" (למשל "*-2"). כדי שזה יעבוד
                                 * בנוחות עם שמות פרמטרים תקינים, ה-Worker ממיר אוטומטית "*" למילה "star",
                                 * "#" למילה "hash", ומוריד מקפים - כך ש-PressKey="*-2" הופך ל"star2", ולכן
                                 * בהגדרות כותבים target_star2 / note_star2 וכו'.
                                 *
                                 * כל פרמטר עם סיומת _<מקש מנורמל> (למשל note_1, dept_2, note_star2) ייחשב
                                 * "שייך" לאותו מקש בלבד. ה-Worker מסנן ומעביר ליעד רק את הפרמטרים ששייכים
                                 * למקש שבאמת נלחץ (עם הסיומת מוסרת), ומשמיט פרמטרים ששייכים למקשים אחרים.
                                 * פרמטרים בלי סיומת כזו (כמו PressKey, ApiExtension, ApiPhone) מועברים כרגיל.
                                 */
                                
                                // תשובת ברירת מחדל לימות אם המקש לא נמצא בפרמטרים (בפורמט הפקודות של ימות)
                                const FALLBACK_RESPONSE = "go_to_folder=";
                                
                                // הופך PressKey כפי שנשלח מימות (כולל אפשרויות כמו "*-2") לסיומת שם פרמטר
                                // בטוחה: "*" -> "star", "#" -> "hash", מקפים מוסרים. לדוגמה: "*-2" -> "star2"
                                function sanitizeKeySuffix(rawKey) {
                                  return String(rawKey)
                                    .trim()
                                    .replace(/\*/g, "star")
                                    .replace(/#/g, "hash")
                                    .replace(/-/g, "");
                                }
                                
                                // בונה את אובייקט הפרמטרים שיישלח ליעד: פרמטרים עם סיומת _<מקש מנורמל> נשארים
                                // רק אם הסיומת תואמת למקש שנלחץ (והסיומת מוסרת), פרמטרים ששייכים למקש אחר
                                // מושמטים, ופרמטרים בלי סיומת כזו מועברים כמו שהם.
                                function buildForwardParams(params, keySuffix) {
                                  const forwarded = {};
                                  for (const [key, value] of Object.entries(params)) {
                                    const match = key.match(/^(.*)_(\d+|star\d*|hash\d*)$/);
                                    if (match) {
                                      const [, baseName, keyNum] = match;
                                      if (keyNum === keySuffix) {
                                        forwarded[baseName] = value;
                                      }
                                      // אחרת - שייך למקש אחר, מושמט בכוונה
                                    } else {
                                      forwarded[key] = value;
                                    }
                                  }
                                  return forwarded;
                                }
                                
                                export default {
                                  async fetch(request) {
                                    const url = new URL(request.url);
                                    let params;
                                
                                    if (request.method === "POST") {
                                      const contentType = request.headers.get("content-type") || "";
                                      if (contentType.includes("application/json")) {
                                        params = await request.json().catch(() => ({}));
                                      } else {
                                        const text = await request.text();
                                        params = Object.fromEntries(new URLSearchParams(text));
                                      }
                                    } else {
                                      params = Object.fromEntries(url.searchParams);
                                    }
                                
                                    // ימות שולח את המקש שנלחץ בפרמטר PressKey (יכול להיות "2" רגיל, או
                                    // "*-2" אם זה מקש דרך תפריט אפשרויות נוספות) - מנרמלים לסיומת בטוחה
                                    const rawPressKey = params.PressKey ?? params.pressKey ?? "";
                                    const pressKey = sanitizeKeySuffix(rawPressKey);
                                    const targetUrl = params[`target_${pressKey}`];
                                
                                    if (!targetUrl) {
                                      return new Response(FALLBACK_RESPONSE, {
                                        status: 200,
                                        headers: { "content-type": "text/plain; charset=utf-8" },
                                      });
                                    }
                                
                                    try {
                                      const forwardUrl = new URL(targetUrl);
                                      const forwardParams = buildForwardParams(params, pressKey);
                                      let forwardResponse;
                                
                                      if (request.method === "POST") {
                                        forwardResponse = await fetch(forwardUrl.toString(), {
                                          method: "POST",
                                          headers: { "content-type": "application/json" },
                                          body: JSON.stringify(forwardParams),
                                        });
                                      } else {
                                        for (const [key, value] of Object.entries(forwardParams)) {
                                          forwardUrl.searchParams.set(key, String(value));
                                        }
                                        forwardResponse = await fetch(forwardUrl.toString(), { method: "GET" });
                                      }
                                
                                      const bodyText = await forwardResponse.text();
                                
                                      return new Response(bodyText, {
                                        status: 200,
                                        headers: { "content-type": "text/plain; charset=utf-8" },
                                      });
                                    } catch (err) {
                                      return new Response(FALLBACK_RESPONSE, {
                                        status: 200,
                                        headers: { "content-type": "text/plain; charset=utf-8" },
                                      });
                                    }
                                  },
                                };
                                
                                

                                וזה אשף להגדרת השלוחה

                                <!DOCTYPE html>
                                <html lang="he" dir="rtl">
                                <head>
                                <meta charset="UTF-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
                                <title>אשף הגדרות ניתוב מקשים - ימות המשיח</title>
                                <link rel="preconnect" href="https://fonts.googleapis.com">
                                <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                                <link href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
                                <style>
                                  :root {
                                    --paper: #FAF7F1;
                                    --panel: #FFFFFF;
                                    --ink: #201F1D;
                                    --ink-soft: #5B5750;
                                    --border: #E4DFD4;
                                    --accent: #1F7A6C;
                                    --accent-ink: #0E3F37;
                                    --accent-soft: #E4F1EE;
                                    --warn: #B3672B;
                                    --warn-soft: #FBEEE0;
                                    --danger: #B3413B;
                                    --danger-soft: #FBEAE8;
                                    --terminal-bg: #17211D;
                                    --terminal-ink: #DDEEE7;
                                    --terminal-accent: #7FD6BE;
                                    --radius: 10px;
                                    --shadow: 0 1px 2px rgba(32,31,29,0.06), 0 6px 16px rgba(32,31,29,0.06);
                                  }
                                  @media (prefers-color-scheme: dark) {
                                    :root:not([data-theme="light"]) {
                                      --paper: #14120F;
                                      --panel: #1D1A16;
                                      --ink: #F1EDE5;
                                      --ink-soft: #B7AF9F;
                                      --border: #35302A;
                                      --accent: #4FBFA8;
                                      --accent-ink: #C9F1E5;
                                      --accent-soft: #1E2E2A;
                                      --warn: #E0A868;
                                      --warn-soft: #2E2418;
                                      --danger: #E08A83;
                                      --danger-soft: #2E1B19;
                                      --terminal-bg: #0E1613;
                                      --terminal-ink: #DDEEE7;
                                      --terminal-accent: #7FD6BE;
                                      --shadow: 0 1px 2px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.35);
                                    }
                                  }
                                  :root[data-theme="dark"] {
                                    --paper: #14120F;
                                    --panel: #1D1A16;
                                    --ink: #F1EDE5;
                                    --ink-soft: #B7AF9F;
                                    --border: #35302A;
                                    --accent: #4FBFA8;
                                    --accent-ink: #C9F1E5;
                                    --accent-soft: #1E2E2A;
                                    --warn: #E0A868;
                                    --warn-soft: #2E2418;
                                    --danger: #E08A83;
                                    --danger-soft: #2E1B19;
                                    --terminal-bg: #0E1613;
                                    --terminal-ink: #DDEEE7;
                                    --terminal-accent: #7FD6BE;
                                    --shadow: 0 1px 2px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.35);
                                  }
                                
                                  * { box-sizing: border-box; }
                                  html { scroll-padding-top: env(safe-area-inset-top, 0px); }
                                  body {
                                    margin: 0;
                                    background: var(--paper);
                                    color: var(--ink);
                                    font-family: 'Heebo', system-ui, sans-serif;
                                    padding-top: env(safe-area-inset-top, 0px);
                                    padding-bottom: env(safe-area-inset-bottom, 0px);
                                    line-height: 1.5;
                                  }
                                  .wrap {
                                    max-width: 1080px;
                                    margin: 0 auto;
                                    padding: 28px 20px 80px;
                                  }
                                
                                  header.page-head {
                                    margin-bottom: 28px;
                                  }
                                  header.page-head .eyebrow {
                                    display: inline-flex;
                                    align-items: center;
                                    gap: 8px;
                                    font-size: 13px;
                                    color: var(--accent-ink);
                                    background: var(--accent-soft);
                                    border: 1px solid var(--accent);
                                    border-radius: 999px;
                                    padding: 4px 12px;
                                    margin-bottom: 14px;
                                  }
                                  header.page-head h1 {
                                    font-size: clamp(24px, 4vw, 34px);
                                    font-weight: 800;
                                    margin: 0 0 8px;
                                    letter-spacing: -0.01em;
                                  }
                                  header.page-head p {
                                    color: var(--ink-soft);
                                    max-width: 62ch;
                                    margin: 0;
                                    font-size: 15px;
                                  }
                                
                                  .grid {
                                    display: grid;
                                    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
                                    gap: 20px;
                                    align-items: start;
                                  }
                                  @media (max-width: 880px) {
                                    .grid { grid-template-columns: 1fr; }
                                  }
                                
                                  .card {
                                    background: var(--panel);
                                    border: 1px solid var(--border);
                                    border-radius: var(--radius);
                                    box-shadow: var(--shadow);
                                    padding: 20px;
                                  }
                                  .card + .card { margin-top: 18px; }
                                
                                  .card h2 {
                                    font-size: 16px;
                                    font-weight: 700;
                                    margin: 0 0 4px;
                                  }
                                  .card .sub {
                                    color: var(--ink-soft);
                                    font-size: 13px;
                                    margin: 0 0 16px;
                                  }
                                
                                  label {
                                    display: block;
                                    font-size: 13px;
                                    font-weight: 500;
                                    color: var(--ink-soft);
                                    margin-bottom: 6px;
                                  }
                                  input[type="text"], input[type="url"] {
                                    width: 100%;
                                    padding: 9px 11px;
                                    border: 1px solid var(--border);
                                    border-radius: 8px;
                                    background: var(--paper);
                                    color: var(--ink);
                                    font-family: inherit;
                                    font-size: 14px;
                                  }
                                  input[type="text"]:focus, input[type="url"]:focus {
                                    outline: 2px solid var(--accent);
                                    outline-offset: 1px;
                                    border-color: var(--accent);
                                  }
                                  .field { margin-bottom: 14px; }
                                  .field-row {
                                    display: flex;
                                    gap: 10px;
                                  }
                                  .field-row .field { flex: 1; margin-bottom: 0; }
                                
                                  .toggle-row {
                                    display: flex;
                                    align-items: center;
                                    gap: 10px;
                                    font-size: 14px;
                                    color: var(--ink);
                                    margin-top: 4px;
                                  }
                                  .toggle-row input { width: 16px; height: 16px; }
                                
                                  /* key rows - styled like small keypad tiles */
                                  .key-row {
                                    border: 1px solid var(--border);
                                    border-radius: 10px;
                                    padding: 14px;
                                    margin-bottom: 14px;
                                    background: var(--paper);
                                    position: relative;
                                  }
                                  .key-row-top {
                                    display: grid;
                                    grid-template-columns: 84px 1fr auto;
                                    gap: 10px;
                                    align-items: end;
                                  }
                                  @media (max-width: 520px) {
                                    .key-row-top { grid-template-columns: 1fr; }
                                  }
                                  .key-cell input {
                                    text-align: center;
                                    font-family: 'JetBrains Mono', monospace;
                                    font-weight: 700;
                                    font-size: 16px;
                                    color: var(--accent-ink);
                                    background: var(--accent-soft);
                                    border-color: var(--accent);
                                  }
                                  .key-hint {
                                    font-size: 11px;
                                    color: var(--ink-soft);
                                    margin-top: 4px;
                                  }
                                  .row-remove {
                                    align-self: end;
                                  }
                                
                                  .extra-params { margin-top: 12px; }
                                  .extra-params-head {
                                    display: flex;
                                    align-items: center;
                                    justify-content: space-between;
                                    margin-bottom: 8px;
                                  }
                                  .extra-params-head span {
                                    font-size: 12px;
                                    font-weight: 600;
                                    color: var(--ink-soft);
                                  }
                                  .extra-param-row {
                                    display: grid;
                                    grid-template-columns: 1fr 1.4fr auto;
                                    gap: 8px;
                                    margin-bottom: 8px;
                                  }
                                  .extra-param-row input { font-size: 13px; padding: 7px 9px; }
                                
                                  button {
                                    font-family: inherit;
                                    cursor: pointer;
                                    border-radius: 8px;
                                    border: 1px solid transparent;
                                    font-size: 13px;
                                    font-weight: 600;
                                    padding: 8px 14px;
                                    transition: transform .05s ease, background .15s ease, border-color .15s ease;
                                  }
                                  button:active { transform: translateY(1px); }
                                
                                  .btn-primary {
                                    background: var(--accent);
                                    color: #ffffff;
                                    border-color: var(--accent);
                                  }
                                  .btn-primary:hover { filter: brightness(1.05); }
                                
                                  .btn-ghost {
                                    background: transparent;
                                    color: var(--ink);
                                    border-color: var(--border);
                                  }
                                  .btn-ghost:hover { border-color: var(--accent); color: var(--accent-ink); }
                                
                                  .btn-danger-ghost {
                                    background: transparent;
                                    color: var(--danger);
                                    border-color: var(--danger);
                                    padding: 6px 10px;
                                    font-size: 12px;
                                  }
                                  .btn-danger-ghost:hover { background: var(--danger-soft); }
                                
                                  .btn-small {
                                    padding: 5px 10px;
                                    font-size: 12px;
                                  }
                                
                                  .add-key-btn {
                                    width: 100%;
                                    padding: 12px;
                                    border: 1.5px dashed var(--border);
                                    background: transparent;
                                    color: var(--ink-soft);
                                    font-weight: 600;
                                  }
                                  .add-key-btn:hover {
                                    border-color: var(--accent);
                                    color: var(--accent-ink);
                                    background: var(--accent-soft);
                                  }
                                
                                  .warn-box, .danger-box {
                                    border-radius: 8px;
                                    padding: 10px 12px;
                                    font-size: 13px;
                                    margin-bottom: 14px;
                                  }
                                  .warn-box { background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn); }
                                  .danger-box { background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger); }
                                
                                  /* output / terminal panel */
                                  .output-card {
                                    position: sticky;
                                    top: 16px;
                                  }
                                  @media (max-width: 880px) {
                                    .output-card { position: static; }
                                  }
                                  .terminal {
                                    background: var(--terminal-bg);
                                    color: var(--terminal-ink);
                                    border-radius: 8px;
                                    padding: 16px;
                                    font-family: 'JetBrains Mono', monospace;
                                    font-size: 12.5px;
                                    line-height: 1.65;
                                    white-space: pre-wrap;
                                    word-break: break-all;
                                    direction: ltr;
                                    text-align: left;
                                    max-height: 60vh;
                                    overflow: auto;
                                  }
                                  .terminal .comment { color: #7C9187; }
                                  .terminal .key { color: var(--terminal-accent); }
                                  .terminal .val { color: var(--terminal-ink); }
                                
                                  .output-actions {
                                    display: flex;
                                    gap: 8px;
                                    margin-top: 12px;
                                  }
                                
                                  .toast {
                                    position: fixed;
                                    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
                                    left: 50%;
                                    transform: translateX(-50%) translateY(20px);
                                    background: var(--ink);
                                    color: var(--paper);
                                    padding: 10px 18px;
                                    border-radius: 999px;
                                    font-size: 13px;
                                    font-weight: 600;
                                    opacity: 0;
                                    pointer-events: none;
                                    transition: opacity .2s ease, transform .2s ease;
                                    z-index: 50;
                                  }
                                  .toast.show {
                                    opacity: 1;
                                    transform: translateX(-50%) translateY(0);
                                  }
                                
                                  .footnote {
                                    font-size: 12px;
                                    color: var(--ink-soft);
                                    margin-top: 10px;
                                  }
                                </style>
                                </head>
                                <body>
                                <div class="wrap">
                                
                                  <header class="page-head">
                                    <span class="eyebrow">☎️ מחולל הגדרות למודול Playfile</span>
                                    <h1>אשף ניתוב מקשים ל-API בימות המשיח</h1>
                                    <p>
                                      הוסיפו מקשים, לכל מקש כתובת Worker יעודית ופרמטרים נוספים - והאשף יבנה עבורכם
                                      את שורות ה-<code>ext.ini</code> המוכנות להדבקה, כולל <code>control_play</code> ו-<code>api_add</code>.
                                    </p>
                                  </header>
                                
                                  <div class="grid">
                                    <div>
                                      <div class="card">
                                        <h2>הגדרות כלליות</h2>
                                        <p class="sub">כתובת ה-Worker אחת בלבד, זהה לכל המקשים - הניתוב עצמו קורה בתוך ה-Worker.</p>
                                
                                        <div class="field">
                                          <label for="workerUrl">כתובת ה-Worker (api_link)</label>
                                          <input type="url" id="workerUrl" placeholder="https://your-worker-subdomain.workers.dev" autocomplete="off">
                                        </div>
                                
                                        <div class="toggle-row">
                                          <input type="checkbox" id="postToggle" checked>
                                          <label for="postToggle" style="margin:0;font-weight:400;color:var(--ink);">שליחה כ-POST (api_url_post=yes)</label>
                                        </div>
                                      </div>
                                
                                      <div class="card">
                                        <h2>מקשים וכתובות יעד</h2>
                                        <p class="sub">
                                          מקש רגיל: תו אחד - <code>1</code>-<code>9</code>, <code>0</code>, <code>*</code> או <code>#</code>.
                                          מקש דרך "אפשרויות נוספות": שני תווים, למשל <code>*2</code> (כוכבית ואז ספרה).
                                        </p>
                                
                                        <div id="warnings"></div>
                                        <div id="keyRows"></div>
                                
                                        <button class="add-key-btn" id="addKeyBtn" type="button">+ הוספת מקש</button>
                                      </div>
                                    </div>
                                
                                    <div class="output-card">
                                      <div class="card">
                                        <h2>תוצאה - להדביק ב-ext.ini</h2>
                                        <p class="sub">מתעדכן אוטומטית עם כל שינוי.</p>
                                        <div class="terminal" id="output" aria-live="polite"></div>
                                        <div class="output-actions">
                                          <button class="btn-primary" id="copyBtn" type="button">העתקת ההגדרות</button>
                                          <button class="btn-ghost" id="resetBtn" type="button">איפוס הכל</button>
                                        </div>
                                        <p class="footnote">השמירה נשמרת אוטומטית בדפדפן הזה בלבד, כדי שלא תאבדו נתונים ברענון.</p>
                                      </div>
                                    </div>
                                  </div>
                                </div>
                                
                                <div class="toast" id="toast">ההגדרות הועתקו</div>
                                
                                <script>
                                (function () {
                                  "use strict";
                                
                                  var STORAGE_KEY = "yemotKeyRouterWizardState";
                                
                                  var state = {
                                    workerUrl: "",
                                    postMode: true,
                                    keys: [
                                      { key: "1", target: "", params: [] },
                                      { key: "2", target: "", params: [] }
                                    ]
                                  };
                                
                                  function loadState() {
                                    try {
                                      var raw = localStorage.getItem(STORAGE_KEY);
                                      if (raw) {
                                        var parsed = JSON.parse(raw);
                                        if (parsed && Array.isArray(parsed.keys) && parsed.keys.length) {
                                          state = parsed;
                                        }
                                      }
                                    } catch (e) { /* קאש לא זמין - ממשיכים עם ברירת מחדל */ }
                                  }
                                
                                  function saveState() {
                                    try {
                                      localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
                                    } catch (e) { /* לא קריטי */ }
                                  }
                                
                                  function sanitizeKeySuffix(rawKey) {
                                    return String(rawKey || "")
                                      .trim()
                                      .replace(/\*/g, "star")
                                      .replace(/#/g, "hash")
                                      .replace(/-/g, "");
                                  }
                                
                                  function keyInfo(rawKey) {
                                    var key = String(rawKey || "").trim();
                                    if (!key) return { valid: false, reason: "מקש ריק" };
                                    if (key.length === 1) {
                                      var okSingle = /^[0-9*#]$/.test(key);
                                      if (!okSingle) return { valid: false, reason: "מקש לא מוכר: " + key };
                                      var suffix = key === "*" ? "star" : key === "#" ? "hash" : key;
                                      return {
                                        valid: true,
                                        controlLine: "control_play" + key + "=send_api",
                                        suffix: suffix,
                                        label: key
                                      };
                                    }
                                    if (key.length === 2) {
                                      var first = key[0], second = key[1];
                                      var firstOk = first === "*" || first === "#";
                                      var secondOk = /^[0-9]$/.test(second);
                                      if (!firstOk || !secondOk) {
                                        return { valid: false, reason: "מקש דו-תווי חייב להיות * או # ואחריו ספרה, למשל *2" };
                                      }
                                      var firstWord = first === "*" ? "star" : "hash";
                                      return {
                                        valid: true,
                                        controlLine: "control_play_moreA" + second + "=send_api",
                                        suffix: firstWord + second,
                                        label: key,
                                        nonDefault: first === "#"
                                      };
                                    }
                                    return { valid: false, reason: "מקש ארוך מדי (עד 2 תווים)" };
                                  }
                                
                                  function el(tag, attrs, children) {
                                    var node = document.createElement(tag);
                                    attrs = attrs || {};
                                    Object.keys(attrs).forEach(function (k) {
                                      if (k === "class") node.className = attrs[k];
                                      else if (k === "text") node.textContent = attrs[k];
                                      else node.setAttribute(k, attrs[k]);
                                    });
                                    (children || []).forEach(function (c) { node.appendChild(c); });
                                    return node;
                                  }
                                
                                  function renderKeyRows() {
                                    var container = document.getElementById("keyRows");
                                    container.innerHTML = "";
                                
                                    state.keys.forEach(function (row, idx) {
                                      var wrap = el("div", { class: "key-row" });
                                
                                      var top = el("div", { class: "key-row-top" });
                                
                                      var keyCell = el("div", { class: "field key-cell" });
                                      keyCell.appendChild(el("label", { text: "מקש" }));
                                      var keyInput = el("input", { type: "text", maxlength: "2", value: row.key });
                                      keyInput.addEventListener("input", function (e) {
                                        row.key = e.target.value;
                                        syncOutputs();
                                      });
                                      keyCell.appendChild(keyInput);
                                      keyCell.appendChild(el("div", { class: "key-hint", text: "לדוגמה: 1 או *2" }));
                                      top.appendChild(keyCell);
                                
                                      var targetCell = el("div", { class: "field" });
                                      targetCell.appendChild(el("label", { text: "כתובת יעד עבור המקש הזה" }));
                                      var targetInput = el("input", { type: "url", placeholder: "https://server.example.com/webhook", value: row.target });
                                      targetInput.addEventListener("input", function (e) {
                                        row.target = e.target.value;
                                        syncOutputs();
                                      });
                                      targetCell.appendChild(targetInput);
                                      top.appendChild(targetCell);
                                
                                      var removeCell = el("div", { class: "field row-remove" });
                                      removeCell.appendChild(el("label", { text: "\u00A0" }));
                                      var removeBtn = el("button", { class: "btn-danger-ghost", type: "button", text: "הסרת מקש" });
                                      removeBtn.addEventListener("click", function () {
                                        state.keys.splice(idx, 1);
                                        update();
                                      });
                                      removeCell.appendChild(removeBtn);
                                      top.appendChild(removeCell);
                                
                                      wrap.appendChild(top);
                                
                                      // extra params
                                      var extra = el("div", { class: "extra-params" });
                                      var extraHead = el("div", { class: "extra-params-head" }, [
                                        el("span", { text: "פרמטרים נוספים למקש זה (לא חובה)" })
                                      ]);
                                      var addParamBtn = el("button", { class: "btn-ghost btn-small", type: "button", text: "+ הוספת פרמטר" });
                                      addParamBtn.addEventListener("click", function () {
                                        row.params.push({ name: "", value: "" });
                                        update();
                                      });
                                      extraHead.appendChild(addParamBtn);
                                      extra.appendChild(extraHead);
                                
                                      row.params.forEach(function (p, pIdx) {
                                        var pRow = el("div", { class: "extra-param-row" });
                                        var nameInput = el("input", { type: "text", placeholder: "שם הפרמטר, למשל note", value: p.name });
                                        nameInput.addEventListener("input", function (e) { p.name = e.target.value; syncOutputs(); });
                                        var valueInput = el("input", { type: "text", placeholder: "ערך, למשל פנייה למכירות", value: p.value });
                                        valueInput.addEventListener("input", function (e) { p.value = e.target.value; syncOutputs(); });
                                        var delBtn = el("button", { class: "btn-danger-ghost btn-small", type: "button", text: "הסרה" });
                                        delBtn.addEventListener("click", function () {
                                          row.params.splice(pIdx, 1);
                                          update();
                                        });
                                        pRow.appendChild(nameInput);
                                        pRow.appendChild(valueInput);
                                        pRow.appendChild(delBtn);
                                        extra.appendChild(pRow);
                                      });
                                
                                      wrap.appendChild(extra);
                                      container.appendChild(wrap);
                                    });
                                  }
                                
                                  function renderWarnings() {
                                    var box = document.getElementById("warnings");
                                    box.innerHTML = "";
                                    var problems = [];
                                    var suffixSeen = {};
                                
                                    state.keys.forEach(function (row) {
                                      var info = keyInfo(row.key);
                                      if (!info.valid && row.key.trim()) {
                                        problems.push({ type: "danger", text: "מקש \"" + row.key + "\": " + info.reason });
                                        return;
                                      }
                                      if (info.valid) {
                                        if (suffixSeen[info.suffix]) {
                                          problems.push({ type: "danger", text: "המקשים \"" + suffixSeen[info.suffix] + "\" ו-\"" + row.key + "\" מתנגשים (אותה הגדרה בפועל)" });
                                        }
                                        suffixSeen[info.suffix] = row.key;
                                        if (info.nonDefault) {
                                          problems.push({ type: "warn", text: "מקש \"" + row.key + "\": כניסה דרך # לתפריט אפשרויות נוספות אינה ברירת המחדל בימות - יש לוודא שהוגדרה כך בשלוחה" });
                                        }
                                      }
                                      if (info.valid && !row.target.trim()) {
                                        problems.push({ type: "warn", text: "למקש \"" + row.key + "\" אין כתובת יעד עדיין" });
                                      }
                                    });
                                
                                    if (!state.workerUrl.trim()) {
                                      problems.push({ type: "warn", text: "לא הוזנה כתובת ה-Worker (api_link)" });
                                    }
                                
                                    problems.forEach(function (p) {
                                      box.appendChild(el("div", { class: p.type === "danger" ? "danger-box" : "warn-box", text: p.text }));
                                    });
                                  }
                                
                                  function escapeForIni(value) {
                                    return String(value).replace(/[\r\n]/g, " ").trim();
                                  }
                                
                                  function buildOutput() {
                                    var lines = [];
                                    lines.push({ c: "comment", t: "; --- הגדרות שלוחה מסוג playfile ---" });
                                    lines.push({ c: "comment", t: "type=playfile" });
                                    if (state.workerUrl.trim()) {
                                      lines.push({ c: "plain", t: "api_link=" + escapeForIni(state.workerUrl) });
                                    } else {
                                      lines.push({ c: "comment", t: "; api_link=<יש להזין כתובת Worker למעלה>" });
                                    }
                                    lines.push({ c: "plain", t: "api_url_post=" + (state.postMode ? "yes" : "no") });
                                    lines.push({ c: "comment", t: "" });
                                    lines.push({ c: "comment", t: "; --- מקשים ---" });
                                
                                    var controlLines = [];
                                    var addLines = [];
                                    var counter = 0;
                                
                                    state.keys.forEach(function (row) {
                                      var info = keyInfo(row.key);
                                      if (!info.valid) return;
                                      controlLines.push(info.controlLine);
                                
                                      if (row.target.trim()) {
                                        addLines.push("api_add_" + counter + "=target_" + info.suffix + "=" + escapeForIni(row.target));
                                        counter++;
                                      }
                                      row.params.forEach(function (p) {
                                        if (p.name.trim() && p.value.trim()) {
                                          var safeName = p.name.trim().replace(/[^A-Za-z0-9_\u05D0-\u05EA]/g, "");
                                          if (safeName) {
                                            addLines.push("api_add_" + counter + "=" + safeName + "_" + info.suffix + "=" + escapeForIni(p.value));
                                            counter++;
                                          }
                                        }
                                      });
                                    });
                                
                                    controlLines.forEach(function (l) { lines.push({ c: "plain", t: l }); });
                                    lines.push({ c: "comment", t: "" });
                                    lines.push({ c: "comment", t: "; --- כתובות יעד ופרמטרים נוספים לכל מקש ---" });
                                    addLines.forEach(function (l) { lines.push({ c: "plain", t: l }); });
                                
                                    return lines;
                                  }
                                
                                  function renderOutput() {
                                    var out = document.getElementById("output");
                                    var lines = buildOutput();
                                    out.innerHTML = "";
                                    lines.forEach(function (line) {
                                      var div = document.createElement("div");
                                      if (line.c === "comment") {
                                        div.className = "comment";
                                        div.textContent = line.t || "\u00A0";
                                      } else {
                                        var eqIdx = line.t.indexOf("=");
                                        if (eqIdx > -1) {
                                          var keySpan = document.createElement("span");
                                          keySpan.className = "key";
                                          keySpan.textContent = line.t.slice(0, eqIdx + 1);
                                          var valSpan = document.createElement("span");
                                          valSpan.className = "val";
                                          valSpan.textContent = line.t.slice(eqIdx + 1);
                                          div.appendChild(keySpan);
                                          div.appendChild(valSpan);
                                        } else {
                                          div.textContent = line.t;
                                        }
                                      }
                                      out.appendChild(div);
                                    });
                                  }
                                
                                  function getOutputText() {
                                    return buildOutput().map(function (l) { return l.t; }).join("\n");
                                  }
                                
                                  // עדכון מלא: כשמבנה השורות משתנה (הוספה/הסרה של מקש או פרמטר)
                                  function update() {
                                    renderKeyRows();
                                    renderWarnings();
                                    renderOutput();
                                    saveState();
                                  }
                                
                                  // עדכון קל: כשרק תוכן שדה משתנה (הקלדה) - לא נוגעים ב-DOM של השורות,
                                  // כדי לא לאבד פוקוס מהשדה שממנו מקלידים
                                  function syncOutputs() {
                                    renderWarnings();
                                    renderOutput();
                                    saveState();
                                  }
                                
                                  function showToast(msg) {
                                    var toast = document.getElementById("toast");
                                    toast.textContent = msg;
                                    toast.classList.add("show");
                                    clearTimeout(showToast._t);
                                    showToast._t = setTimeout(function () {
                                      toast.classList.remove("show");
                                    }, 1800);
                                  }
                                
                                  document.getElementById("workerUrl").addEventListener("input", function (e) {
                                    state.workerUrl = e.target.value;
                                    syncOutputs();
                                  });
                                  document.getElementById("postToggle").addEventListener("change", function (e) {
                                    state.postMode = e.target.checked;
                                    update();
                                  });
                                  document.getElementById("addKeyBtn").addEventListener("click", function () {
                                    state.keys.push({ key: "", target: "", params: [] });
                                    update();
                                  });
                                  document.getElementById("resetBtn").addEventListener("click", function () {
                                    if (!confirm("לאפס את כל ההגדרות?")) return;
                                    state = {
                                      workerUrl: "",
                                      postMode: true,
                                      keys: [{ key: "1", target: "", params: [] }]
                                    };
                                    update();
                                  });
                                  document.getElementById("copyBtn").addEventListener("click", function () {
                                    var text = getOutputText();
                                    if (navigator.clipboard && navigator.clipboard.writeText) {
                                      navigator.clipboard.writeText(text).then(function () {
                                        showToast("ההגדרות הועתקו");
                                      }).catch(function () {
                                        fallbackCopy(text);
                                      });
                                    } else {
                                      fallbackCopy(text);
                                    }
                                  });
                                
                                  function fallbackCopy(text) {
                                    var ta = document.createElement("textarea");
                                    ta.value = text;
                                    ta.style.position = "fixed";
                                    ta.style.opacity = "0";
                                    document.body.appendChild(ta);
                                    ta.select();
                                    try {
                                      document.execCommand("copy");
                                      showToast("ההגדרות הועתקו");
                                    } catch (e) {
                                      showToast("לא ניתן להעתיק אוטומטית - יש לסמן ולהעתיק ידנית");
                                    }
                                    document.body.removeChild(ta);
                                  }
                                
                                  loadState();
                                  document.getElementById("workerUrl").value = state.workerUrl;
                                  document.getElementById("postToggle").checked = state.postMode;
                                  update();
                                })();
                                </script>
                                </body>
                                </html>
                                
                                
                                י תגובה 1 תגובה אחרונה תגובה ציטוט 1
                                • י מנותק
                                  יצחק י. @הדיבל המעופף
                                  נערך לאחרונה על ידי

                                  @הדיבל-המעופף תודה רבה,
                                  איזה שרת חינמי מומלץ?


                                  @הדיבל-המעופף אני רואה שניתן להגדיר במודול מקש תוך כדי השמעה, ומקש בתפריט כוכבית (target_star1)
                                  אני רוצה להגדיר במקש control_after_play_moreAX
                                  בתפריט לאחר ההשמעה (after_play_tfr=tfr_more_options)
                                  תודה

                                  ה תגובה 1 תגובה אחרונה תגובה ציטוט 0
                                  • ה מחובר
                                    הדיבל המעופף @יצחק י.
                                    נערך לאחרונה על ידי הדיבל המעופף

                                    @יצחק-י.
                                    אני משתמש ב Cloudflare
                                    הוא די נחמד

                                    ולגבי זה

                                    @יצחק-י. כתב:

                                    אני רוצה להגדיר במקש control_after_play_moreAX
                                    בתפריט לאחר ההשמעה (after_play_tfr=tfr_more_options)

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

                                    אם אתה מסתבך אם השרת אני יכול לשלוח לך כתובת במייל

                                    ולמה אני לא מצליח לתייג אותך?

                                    י 3 תגובות תגובה אחרונה תגובה ציטוט 0
                                    • י מנותק
                                      יצחק י. @הדיבל המעופף
                                      נערך לאחרונה על ידי יצחק י.

                                      @הדיבל-המעופף כתב:

                                      ולמה אני לא מצליח לתייג אותך?

                                      אתה כותב @ וזה לא מופיע?

                                      ה תגובה 1 תגובה אחרונה תגובה ציטוט 0
                                      • ה מחובר
                                        הדיבל המעופף @יצחק י.
                                        נערך לאחרונה על ידי

                                        @יצחק-י. זה מופיע ברשימה הנפתחת אבל לא נהיה כחול???

                                        י תגובה 1 תגובה אחרונה תגובה ציטוט 0
                                        • י מנותק
                                          יצחק י. @הדיבל המעופף
                                          נערך לאחרונה על ידי

                                          @הדיבל-המעופף צילום מסך בבקשה

                                          ה תגובה 1 תגובה אחרונה תגובה ציטוט 0
                                          • ה מחובר
                                            הדיבל המעופף @יצחק י.
                                            נערך לאחרונה על ידי

                                            @יצחק-י.
                                            אתה כן רואה בכחול?

                                            a329d89f-3f09-4a97-b06f-b159bd0bcba0-image.jpeg

                                            י 2 תגובות תגובה אחרונה תגובה ציטוט 0

                                            שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.

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

                                            בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗

                                            הרשמה התחברות
                                            • פוסט ראשון
                                              פוסט אחרון