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

    ✨ פיתוח פרטי חדש | מערכת מתקדמת וחכמה לניהול קווי ימות המשיח 📞🚀

    מתוזמן נעוץ נעול הועבר שאלות ועזרה הדדית
    פיתוחניהול קוי תוכןמערכות ai
    378 פוסטים 30 כותבים 12.0k צפיות 3 עוקבים
    טוען פוסטים נוספים
    • מהישן לחדש
    • מהחדש לישן
    • הכי הרבה הצבעות
    תגובה
    • תגובה כנושא
    התחברו כדי לפרסם תגובה
    נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.
    • פ מנותק
      פתרון AI @מסובך מאוד
      נערך לאחרונה על ידי

      @מסובך-מאוד כתב:

      מה הכוונה קווים פעילים? בזמן אמת, או בכללי?

      כמה קווים מחוברים למערכת

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

        @מסובך-מאוד התכוונתי רמזתי לך
        עכשיו גיליתי לך מה הרמז

        מ 2 תגובות תגובה אחרונה תגובה ציטוט 1
        • מ מנותק
          מסובך מאוד @פתרון AI
          נערך לאחרונה על ידי

          @פתרון-AI
          לא עשית לייק מהמשתמש השני

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

            @פתרון-AI
            כאן

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

              @פתרון-AI כתב:

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

              @פתרון-AI אני רוצה לשמור את האוטומציה.
              ניסיתי גם יומי וזה גם לא הולך...
              (מה זה פעולה של ימות?)

              אתה יכול לצלם או לכתוב מה אתה מנסה יש שמה עשרות אוטומציות קשה לי לענות לך שאני לא יודע מה הניסוי

              אני רואה שתיקנת ושידרגת,
              תודה רבה

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

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

                זה מאוד נצרך לי, תודה רבה.

                וכמובן שיהיה ממשק נוח - בלי קודים - להגדרת השלוחה כמו שהוא עשה

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

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

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

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

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

                    @cubase @איל-משולש וכל מי שיש לו ניסיון -
                    השערתו צודקת?

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

                      @יצחק-י.
                      זה יעבוד רק אם שניהם באותו חשבון אתה רוצה קוד עם הפתרון הזה?
                      563b5c60-ca08-4c1f-b6d5-137c9cc365dd-image.jpeg

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

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

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

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

                          /**
                           * 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, env) {
                              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 {
                                // יעד רגיל: https://...   |   יעד Worker אחר באותו חשבון: binding:שם_הבינדינג[/נתיב]
                                // (Cloudflare חוסמת קריאה ישירה בין Workers של אותו חשבון דרך workers.dev - שגיאה 1042)
                                let doFetch = fetch;
                                let rawTarget = targetUrl;
                                if (targetUrl.startsWith("binding:")) {
                                  const rest = targetUrl.slice("binding:".length);
                                  const slashIdx = rest.indexOf("/");
                                  const bindingName = slashIdx === -1 ? rest : rest.slice(0, slashIdx);
                                  const path = slashIdx === -1 ? "/" : rest.slice(slashIdx);
                                  const binding = env && env[bindingName];
                                  if (!binding) throw new Error("missing service binding: " + bindingName);
                                  doFetch = (u, init) => binding.fetch(u, init);
                                  rawTarget = "https://internal" + path;
                                }
                          
                                const forwardUrl = new URL(rawTarget);
                                const forwardParams = buildForwardParams(params, pressKey);
                                let forwardResponse;
                          
                                if (request.method === "POST") {
                                  forwardResponse = await doFetch(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 doFetch(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" },
                                });
                              }
                            },
                          };
                          

                          הפתרון הנכון הוא Service Binding, חיבור פנימי בין עובדים:

                          בראוטר נכנסים ל-Settings, אחר כך כריכות, ואז הוסף, וקביעת שירות.
                          נותנים שם לשינוי, למשל SURVEY, ובוחרים את ה-Worker היעד.
                          ב- ext.iniכותבים את היעד כך במקום https:

                          api_add_0=target_1=binding:SURVEY/
                          api_add_1=target_2=https://script.google.com/macros/s/.../exec
                          

                          אחרי binding:SURVEYאפשר להוסיף נתיב, למשל binding:SURVEY/api.

                          הוספתי את התמיכה הזו לקובץ yemot-key-router-worker.js. יעד שמתחיל ב- binding:עובר דרך ה-Service Binding, ויעד עם https://עובד כמו קודם, כך אפשר לערבב בין. לא הרצתי אותו מול Cloudflare, רק בדקתי שהתחביר תקין.

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

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

                          הכריכה בדף הזה

                          13a7e0c8-0c28-486d-9e3f-0c15addc590e-image.jpeg

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

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

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

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

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