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

    שלוחת סקר - קלה ופשוטה לשימוש !!!

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

      שים לב! בשביל השלוחה צריך שרת

      אפשר לנסות בקו 0799256584 שלוחה 55

      קוד השלוחה

      title=סקר לדוגמא
      type=api
      api_link=כתובת השרת
      api_add_0=token=טוקן
      

      בשלוחת הסקר יש ליצור קובץ בשם Surveyquestion.ini בו נגדיר את השאלות

      שאלה
      question=האם אתם הולכים לישון מוקדם אם כן הקישו אחד אם לא הקישו שתיים
      תשובות
      possibility1=כן
      possibility2=לא
      
      

      הקוד לשרת

      /**
       * survey-worker.js
       * -----------------
       * שלוחת סקר לימות המשיח (מודול API, type=api) + נקודת קצה לדשבורד ניהול.
       *
       * שני קבצים נשמרים בתיקיית השלוחה (ivr2), דרך ה-API הרגיל למפתחים
       * (הפקודות GetTextFile / UploadTextFile):
       *
       *   Surveyquestion.ini   - מוגדר ידנית מראש, מכיל את השאלה והאפשרויות.
       *   Surveydata.ini       - נכתב אוטומטית על ידי ה-Worker, שורה לכל מצביע:
       *                          <טלפון>,<מספר_אפשרות>,<תאריך_ISO>
       *
       * הגדרות נדרשות ב-ext.ini של השלוחה:
       *
       *   type=api
       *   api_link=https://YOUR-WORKER.workers.dev/survey
       *   api_add_0=token=YOUR_YEMOT_DEV_API_TOKEN
       *
       * (ה-token הוא טוקן ה-API הקבוע של ימות המשיח - "חומת האש" - המשמש
       *  את ה-Worker לקרוא ולכתוב את קבצי ה-ini באמצעות ה-API הרגיל למפתחים)
       */
      
      const YEMOT_API_BASE = "https://www.call2all.co.il/ym/api/";
      
      // ---------- עזרים כלליים ----------
      
      function corsHeaders() {
        return {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
          "Access-Control-Allow-Headers": "Content-Type",
        };
      }
      
      function textResponse(body) {
        return new Response(body, {
          status: 200,
          headers: { "Content-Type": "text/plain; charset=utf-8" },
        });
      }
      
      function jsonResponse(obj, status = 200) {
        return new Response(JSON.stringify(obj), {
          status,
          headers: {
            "Content-Type": "application/json; charset=utf-8",
            ...corsHeaders(),
          },
        });
      }
      
      // מנקה תווים שימות המשיח לא מאפשר בטקסט להשמעה (t-) ותווים
      // שעלולים לשבש את תחביר תשובת השרת (& = )
      function sanitizeText(s) {
        return (s || "")
          .replace(/[.\-&=]/g, " ")
          .replace(/\s+/g, " ")
          .trim();
      }
      
      // פרסור קובץ ini פשוט (key=value בכל שורה) - עבור Surveyquestion.ini
      function parseIni(text) {
        const obj = {};
        if (!text) return obj;
        const lines = text.split(/\r?\n/);
        for (const raw of lines) {
          const line = raw.trim();
          if (!line || line.startsWith(";") || line.startsWith("#")) continue;
          const idx = line.indexOf("=");
          if (idx === -1) continue;
          const key = line.slice(0, idx).trim();
          const val = line.slice(idx + 1).trim();
          obj[key] = val;
        }
        return obj;
      }
      
      // פרסור Surveydata.ini - שורה לכל מצביע: phone,choice,isoTime
      function parseSurveyData(text) {
        const votes = [];
        if (!text) return votes;
        const lines = text.split(/\r?\n/);
        for (const raw of lines) {
          const line = raw.trim();
          if (!line) continue;
          const parts = line.split(",");
          if (parts.length < 2) continue;
          votes.push({
            phone: (parts[0] || "").trim(),
            choice: (parts[1] || "").trim(),
            time: (parts[2] || "").trim(),
          });
        }
        return votes;
      }
      
      function buildIvrPath(apiExtension, filename) {
        let ext = apiExtension || "";
        if (!ext.startsWith("/")) ext = "/" + ext;
        return `ivr2:${ext}/${filename}`;
      }
      
      // שולף פרמטרים גם מ-query string וגם מגוף הבקשה (JSON או form), כדי
      // לתמוך בשני מצבי השליחה של מודול ה-API (GET רגיל, או api_url_post=yes)
      async function extractParams(request) {
        const url = new URL(request.url);
        const params = {};
        for (const [k, v] of url.searchParams.entries()) params[k] = v;
      
        if (request.method === "POST") {
          const contentType = request.headers.get("content-type") || "";
          try {
            if (contentType.includes("application/json")) {
              const body = await request.json();
              Object.assign(params, body);
            } else {
              const bodyText = await request.text();
              if (bodyText) {
                const bodyParams = new URLSearchParams(bodyText);
                for (const [k, v] of bodyParams.entries()) params[k] = v;
              }
            }
          } catch (e) {
            // גוף ריק / לא תקין - מתעלמים, נשארים עם מה שהיה ב-query string
          }
        }
        return params;
      }
      
      // ---------- קריאות ל-API הרגיל למפתחים של ימות המשיח ----------
      
      async function callYemotApi(command, params) {
        const resp = await fetch(YEMOT_API_BASE + command, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(params),
        });
        return resp.json();
      }
      
      async function getTextFile(token, path) {
        const data = await callYemotApi("GetTextFile", { token, what: path });
        if (data && data.responseStatus === "OK") {
          return data.contents || "";
        }
        return "";
      }
      
      async function uploadTextFile(token, path, contents) {
        const data = await callYemotApi("UploadTextFile", {
          token,
          what: path,
          contents,
        });
        return data && data.responseStatus === "OK";
      }
      
      // ---------- בניית תשובות לימות המשיח ----------
      
      function idListMessage(text) {
        return `id_list_message=t-${sanitizeText(text)}`;
      }
      
      // שואל את השאלה ומבקש הקשה יחידה (1..N לפי מספר האפשרויות)
      function readDirective(questionText, allowedKeys) {
        const q = sanitizeText(questionText);
        // name,use_existing,max,min,wait,display,block_star,block_zero,key_replace,allowed_keys,repeat,ok_if_empty,empty_value,kb_block,confirm
        const paramDef = `Vote,,1,1,10,NO,yes,yes,,${allowedKeys},,,,,no`;
        return `read=t-${q}=${paramDef}`;
      }
      
      // משמיע את הבחירה שנקלטה ומבקש אישור/ביטול (1=אישור, 2=ביטול)
      function confirmReadDirective(chosenOptionText) {
        const text = sanitizeText(
          `ההצבעה שלך היא ${chosenOptionText} לאישור הקישו אחד לביטול הקישו שתיים`
        );
        const paramDef = `Confirm,,1,1,10,NO,yes,yes,,12,,,,,no`;
        return `read=t-${text}=${paramDef}`;
      }
      
      function getOptions(questionData) {
        const options = [];
        let i = 1;
        while (questionData["possibility" + i]) {
          options.push({ num: String(i), text: questionData["possibility" + i] });
          i++;
        }
        return options;
      }
      
      // myChoiceNum - אם ידוע מה המצביע הנוכחי בחר (הצבעה חדשה או חוזרת),
      // המשפט "ההצבעה שלך היא..." יתווסף בתחילת ההודעה
      function buildResultsText(questionData, votes, myChoiceNum) {
        const options = getOptions(questionData);
        const total = votes.length;
        const counts = {};
        for (const opt of options) counts[opt.num] = 0;
        for (const v of votes) {
          if (counts[v.choice] !== undefined) counts[v.choice]++;
        }
      
        const parts = [];
        if (myChoiceNum) {
          const myOpt = options.find((o) => o.num === String(myChoiceNum));
          if (myOpt) parts.push(`ההצבעה שלך היא ${myOpt.text}`);
        }
      
        parts.push("תוצאות הסקר עד כה");
        for (const opt of options) {
          const c = counts[opt.num] || 0;
          const pct = total > 0 ? Math.round((c / total) * 100) : 0;
          parts.push(`לאפשרות ${opt.text} הצביעו ${pct} אחוזים`);
        }
        parts.push(`מספר המצביעים עד כה הוא ${total}`);
        return parts.join(", ");
      }
      
      // ---------- הלוגיקה הראשית של שלוחת הסקר ----------
      
      async function handleSurveyRequest(request) {
        const params = await extractParams(request);
        const token = params.token;
        const apiExtension = params.ApiExtension || "";
        const phone = params.ApiPhone || "";
        const callId = params.ApiCallId || "";
        const vote = params.Vote;
        const confirm = params.Confirm;
      
        if (!token) {
          return textResponse(idListMessage("שגיאה חסר טוקן התחברות למערכת"));
        }
        if (!apiExtension) {
          return textResponse(idListMessage("שגיאה לא זוהתה שלוחה"));
        }
      
        const qPath = buildIvrPath(apiExtension, "Surveyquestion.ini");
        const dPath = buildIvrPath(apiExtension, "Surveydata.ini");
      
        const [qText, dText] = await Promise.all([
          getTextFile(token, qPath),
          getTextFile(token, dPath),
        ]);
      
        const questionData = parseIni(qText);
        if (!questionData.question || !questionData.possibility1) {
          return textResponse(
            idListMessage("שגיאה לא נמצאה שאלת סקר מוגדרת בקובץ השאלון")
          );
        }
      
        const votes = parseSurveyData(dText);
        // מזהה ייחודי למצביע: מספר טלפון, ואם אין (מספר חסום וכו') - לפי מזהה השיחה
        const voterKey = phone || "CALL-" + callId;
        const alreadyVoted = votes.some((v) => v.phone === voterKey);
      
        const options = getOptions(questionData);
        const allowedKeys = options.map((o) => o.num).join("");
      
        // מי שכבר הצביע בעבר, ומתחיל שיחה חדשה (אין עדיין Vote/Confirm בבקשה)
        // - משמיעים לו את ההצבעה שלו ואת התוצאות, בלי לשאול שוב
        if (alreadyVoted && !vote) {
          const existing = votes.find((v) => v.phone === voterKey);
          const myChoiceNum = existing ? existing.choice : null;
          return textResponse(
            idListMessage(buildResultsText(questionData, votes, myChoiceNum))
          );
        }
      
        // שלב ג: המשתמש כבר ענה על שאלת האישור (Vote + Confirm)
        if (vote && confirm !== undefined && confirm !== "") {
          if (alreadyVoted) {
            // הספיק להצביע כבר בינתיים (למשל שיחה כפולה) - פשוט משמיעים תוצאות
            const existing = votes.find((v) => v.phone === voterKey);
            const myChoiceNum = existing ? existing.choice : vote;
            return textResponse(
              idListMessage(buildResultsText(questionData, votes, myChoiceNum))
            );
          }
      
          if (String(confirm) === "1") {
            // אושר - שומרים את ההצבעה ומשמיעים תוצאות
            const newLine = `${voterKey},${vote},${new Date().toISOString()}`;
            const newText =
              (dText && dText.trim() ? dText.trim() + "\n" : "") + newLine + "\n";
            await uploadTextFile(token, dPath, newText);
            votes.push({ phone: voterKey, choice: String(vote) });
            return textResponse(
              idListMessage(buildResultsText(questionData, votes, vote))
            );
          }
      
          // בוטל (הוקש 2) - לא שומרים כלום, שואלים את השאלה מחדש
          return textResponse(readDirective(questionData.question, allowedKeys));
        }
      
        // שלב ב: המשתמש הקיש תשובה ראשונה (Vote בלבד) - מבקשים אישור
        if (vote) {
          const chosenOption = options.find((o) => o.num === String(vote));
          if (!chosenOption) {
            // הקשה לא תקינה (לא אמור לקרות בזכות allowed_keys, אבל ליתר ביטחון)
            return textResponse(readDirective(questionData.question, allowedKeys));
          }
          return textResponse(confirmReadDirective(chosenOption.text));
        }
      
        // שלב א: כניסה ראשונה לשלוחה - שואלים את השאלה
        return textResponse(readDirective(questionData.question, allowedKeys));
      }
      
      // ---------- נקודת קצה לדשבורד הניהול (HTML מקומי) ----------
      
      async function handleAdminData(request) {
        const url = new URL(request.url);
        const token = url.searchParams.get("token");
        const ext = url.searchParams.get("ext");
      
        if (!token || !ext) {
          return jsonResponse({ error: "חסר token או ext" }, 400);
        }
      
        const qPath = buildIvrPath(ext, "Surveyquestion.ini");
        const dPath = buildIvrPath(ext, "Surveydata.ini");
      
        const [qText, dText] = await Promise.all([
          getTextFile(token, qPath),
          getTextFile(token, dPath),
        ]);
      
        const questionData = parseIni(qText);
        const votes = parseSurveyData(dText);
        const options = getOptions(questionData);
        const total = votes.length;
      
        const counts = {};
        for (const opt of options) counts[opt.num] = 0;
        for (const v of votes) {
          if (counts[v.choice] !== undefined) counts[v.choice]++;
          else counts[v.choice] = (counts[v.choice] || 0) + 1;
        }
      
        const optionsWithStats = options.map((opt) => ({
          num: opt.num,
          text: opt.text,
          count: counts[opt.num] || 0,
          percent: total > 0 ? Math.round(((counts[opt.num] || 0) / total) * 100) : 0,
        }));
      
        // מיון מצביעים מהחדש לישן
        const sortedVotes = [...votes].sort((a, b) =>
          (b.time || "").localeCompare(a.time || "")
        );
      
        return jsonResponse({
          question: questionData.question || null,
          options: optionsWithStats,
          total,
          votes: sortedVotes.map((v) => ({
            phone: v.phone,
            choiceNum: v.choice,
            choiceText:
              (options.find((o) => o.num === v.choice) || {}).text || v.choice,
            time: v.time,
          })),
        });
      }
      
      // ---------- Router ----------
      
      export default {
        async fetch(request) {
          if (request.method === "OPTIONS") {
            return new Response(null, { headers: corsHeaders() });
          }
      
          const url = new URL(request.url);
          if (url.pathname === "/admin/data") {
            return handleAdminData(request);
          }
      
          // כל נתיב אחר (כולל /survey וגם /) - נחשב כקריאה מהשלוחה בימות המשיח
          return handleSurveyRequest(request);
        },
      };
      
      

      ממשק לצפייה בתוצאות ולהגדרת השאלות יש למחוק את "מיותר"

      תגובות

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

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

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

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

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