// ═══════════════════════════════════════════════════════════════════════════════
// תמלול שיעורים — מערכת תמלול אוטומטית
// מבוססת Gemini API | הגדרות דרך גליון | טריגר אוטומטי
// ═══════════════════════════════════════════════════════════════════════════════
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ פונקציות גלויות: │
// │ 1. יצירתגליון — יוצר את גליון ההגדרות עם כל הדפים │
// │ 2. הפעלהראשונה — מגדיר טריגרים לפי ההגדרות בגליון │
// │ 3. הרצתתמלול — מריץ בדיקת תמלול ידנית │
// │ 4. כיבוי — מוחק את כל הטריגרים │
// └─────────────────────────────────────────────────────────────────────────────┘
const MODELS_TRANSCRIBE = [
"gemini-3.5-flash",
"gemini-3.5-flash-lite",
"gemini-2.5-flash",
"gemini-2.5-pro",
"gemini-2.5-flash-lite",
"gemini-3-flash-preview",
"gemini-3.1-pro-preview",
"gemma-4-31b"
];
const TRANSCRIBE_PROMPT = `אתה מתמלל שיעור תורני (גמרא, הלכה, אגדה, חסידות, מחשבה, תנ"ך). עליך להבין את ההקשר התורני ולתמלל נכון מונחים, שמות וחיבורים תורניים.
הנחיות:
1. תמלל במדויק את כל שנאמר, וערוך לשונית תוך הבנת ההקשר התורני.
2. חלק לפסקאות לפי נושאים או הפסקות טבעיות.
3. הוסף סימני פיסוק מתאימים.
4. הוסף כותרת ראשית אחת בתחילת התמלול.
5. כותרות ביניים — רק למעבר ממשי לנושא חדש (2-4 לשיעור).
6. אל תוסיף סיכום בסוף.
7. הכל בעברית מימין לשמאל.
8. אם קטע לא ברור — כתוב [לא ברור] ואל תמציא.
9. אל תמציא מילים שלא נאמרו.
10. אסור לקצר את התוכן!
11. אסור לחזור על אותו תוכן פעמיים.
12. הגיה ישיבתית ומבטא אשכנזי: ייתכן שהדובר הוגה קמץ כ"או" (קומץ) וצרי כ"אי" (ביית). גם מילים שנשמעות עם חולם (כגון "רובא", "אביי") — יתכן שהכוונה ל"רבא" (שם אמורא), "אבָּיֵי" וכד'. כתוב תמיד בעברית תקנית בלבד.
13. זיהוי תורני וראשי תיבות: זהה נכון מונחים ושמות מפרשים, וכתוב אותם תמיד עם גרשיים (ראשי תיבות): רש"י, רמב"ם, רמב"ן, רא"ש, ב"ח, ט"ז, מהרש"א, ריטב"א, רשב"א, מ"ב (משנה ברורה), ש"ך וכו'. גם ד"ה (דיבור המתחיל), וכו' (וכולי/ועוד), ע"ש (עיין שם), שו"ע (שולחן ערוך), ב"ק (בבא קמא) וכד'.
14. ארמית: קטעים בשפה הארמית (בגמרא, בזוהר) צריכים להופיע בארמית המקורית, לא מתורגמים לעברית.
15. מבנה שיעור: זהה מבנה של שיעור — שאלה פותחת, הבאת מקורות, קושיות ותירוצים, ביאור שיטות, סיכום ביניים, מסקנה — ותמלל תוך שמירה על המבנה הלוגי.
16. ידע תורני: השתמש בידע התורני שלך כדי להבין מילים שנאמרו — במיוחד מונחים, ציטוטי מקורות ושמות. אם הדובר ציטט מקור מוכר והציטוט נשמע חלקית — השתמש בידיעתך לתמלל נכון. אם קטע שלם לא ברור — כתוב [לא ברור] ואל תשלים.
17. ציון דפים בגמרא ובספרים: כשהדובר אומר "דף X עמוד א" — כתוב X. (עם נקודה). כשהדובר אומר "דף X עמוד ב" — כתוב X: (עם נקודתיים). לדוגמה: דף ג עמוד א = ג. | דף ג עמוד ב = ג: | דף כד עמוד א = כד. | דף כד עמוד ב = כד:
18. המילה "וכולה" / "וכולי" = ועוד. כתוב: וכו'.
19. ציון בהירות: בסוף התמלול, הוסף שורה: "בהירות: X/10" (כש-X הוא ציון מ-1 עד 10 המשקף כמה ברור ונשמע טוב ההקלטה).
פקודות קוליות מיוחדות — כשהדובר אומר את הביטויים הבאים, בצע את הפעולה ואל תכתוב את המילים עצמן:
• "פתח כותרת" — התחל כותרת (## ) מכאן. השמט את הביטוי.
• "סגור כותרת" — סיים את הכותרת. השמט את הביטוי.
• "פתח סוגריים" — הכנס ( . השמט את הביטוי.
• "סגור סוגריים" — הכנס ) . השמט את הביטוי.
• "פתח אותיות" — מעכשיו כל אות שנאמרת נכתבת כתו בודד ברצף (בלי רווחים). "רווח" = רווח. השמט "פתח אותיות".
• "סגור אותיות" — חזור לתמלול רגיל (מילים שלמות). השמט "סגור אותיות".
• "מחק משפט אחרון" — מחק את המשפט האחרון שתמללת. השמט את הפקודה.
• "מחק X משפטים" (X = מספר) — מחק את X המשפטים האחרונים שתמללת. השמט את הפקודה.
• "שורה חדשה" — ירידת שורה (לא פסקה חדשה). השמט את הפקודה.
• "פסקה חדשה" — התחל פסקה חדשה. השמט את הפקודה.
• "פתח הדגשה" — התחל טקסט מודגש (**). השמט את הפקודה.
• "סגור הדגשה" — סיים טקסט מודגש (**). השמט את הפקודה.
• "פתח ציטוט" — התחל ציטוט (> ). השמט את הפקודה.
• "סגור ציטוט" — סיים ציטוט. השמט את הפקודה.
• "נקודה" — הכנס נקודה (.) . השמט את הפקודה.
• "פסיק" — הכנס פסיק (,) . השמט את הפקודה.
• "סימן שאלה" — הכנס סימן שאלה (?) . השמט את הפקודה.
• "מקף" — הכנס מקף (—) . השמט את הפקודה.
פורמט הפלט:
**[כותרת ראשית]**
[תוכן...]
בהירות: X/10`;
// ═══════════════════════════════════════════════════════════════════════════════
// קריאת הגדרות מהגליון
// ═══════════════════════════════════════════════════════════════════════════════
function getSpreadsheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
if (ss) return ss;
var id = PropertiesService.getScriptProperties().getProperty("SPREADSHEET_ID");
if (id) {
try { return SpreadsheetApp.openById(id); } catch(e) {}
}
throw new Error("לא נמצא גליון — הרץ יצירתגליון");
}
function getSettings() {
var ss = getSpreadsheet();
var sheet = ss.getSheetByName("הגדרות");
if (!sheet) throw new Error("לא נמצא דף הגדרות — הרץ יצירתגליון");
var data = sheet.getDataRange().getValues();
var settings = {};
// קריאת ערכים מעמודה B (הערכים) לפי שורה
settings.apiKeys = [];
for (var i = 2; i <= 6; i++) { // שורות 3-7 (מפתחות API)
var val = String(data[i] && data[i][1] || "").trim();
if (val) settings.apiKeys.push(val);
}
settings.email = String(data[7] && data[7][1] || "").trim(); // שורה 8
settings.subjects = String(data[8] && data[8][1] || "").trim().split(",").map(function(s){return s.trim();}).filter(Boolean); // שורה 9
settings.autoEmails = String(data[9] && data[9][1] || "").trim().split(",").map(function(s){return s.trim();}).filter(Boolean); // שורה 10
settings.ymToken = String(data[10] && data[10][1] || "").trim(); // שורה 11
settings.ymExtension = String(data[11] && data[11][1] || "").trim(); // שורה 12
settings.intervalMinutes = parseInt(data[12] && data[12][1]) || 5; // שורה 13
return settings;
}
// ═══════════════════════════════════════════════════════════════════════════════
// פונקציות עזר
// ═══════════════════════════════════════════════════════════════════════════════
function escHtml(str) { return String(str).replace(/&/g,'&').replace(//g,'>'); }
function isAudioFile(att) {
var name = att.getName().toLowerCase(), type = (att.getContentType()||"").toLowerCase();
return [".mp3",".wav",".aac",".flac",".ogg",".aiff",".m4a"].some(function(e){return name.endsWith(e);}) || type.startsWith("audio/");
}
function guessMimeType(fileName) {
var map = {".mp3":"audio/mp3",".wav":"audio/wav",".aac":"audio/aac",".flac":"audio/flac",".ogg":"audio/ogg",".aiff":"audio/aiff",".m4a":"audio/mp4"};
for (var ext in map) { if (fileName.toLowerCase().endsWith(ext)) return map[ext]; }
return "audio/mp3";
}
function parseGeminiResponse(response) {
var sc = response.getResponseCode(), rt = response.getContentText();
if (sc !== 200) throw new Error("API Error ("+sc+"): "+(JSON.parse(rt).error||{}).message||"Unknown");
var data = JSON.parse(rt);
if (!data.candidates || !data.candidates.length) throw new Error("אין תוצאות");
var c = data.candidates[0];
if (c.finishReason === "SAFETY") throw new Error("נחסם");
if (!c.content || !c.content.parts) throw new Error("ריק");
return c.content.parts.map(function(p){return p.text;}).join("\n");
}
function hasAlreadyReplied(thread) {
try {
var messages = thread.getMessages();
if (!messages || messages.length <= 1) return false;
var myEmail = Session.getActiveUser().getEmail();
for (var i = messages.length - 1; i >= 1; i--) {
var from = messages[i].getFrom();
if (from && (from.includes(myEmail) || from.includes("תמלול"))) return true;
}
} catch (e) {}
return false;
}
// ═══════════════════════════════════════════════════════════════════════════════
// תמלול — קריאה ל-API
// ═══════════════════════════════════════════════════════════════════════════════
function getActivePrompt() {
var ss = getSpreadsheet();
var sheet = ss.getSheetByName("הנחיות");
if (!sheet) return TRANSCRIBE_PROMPT;
var data = sheet.getDataRange().getValues();
var lines = [];
for (var i = 1; i < data.length; i++) {
var line = String(data[i][0] || "").trim();
if (line) lines.push(line);
}
return lines.length > 0 ? lines.join("\n") : TRANSCRIBE_PROMPT;
}
function transcribeInline(audioBytes, mimeType, apiKey, model, prompt) {
var resp = UrlFetchApp.fetch("https://generativelanguage.googleapis.com/v1beta/models/"+model+":generateContent?key="+apiKey, {
method:"POST", contentType:"application/json",
payload:JSON.stringify({contents:[{parts:[{text:prompt},{inline_data:{mime_type:mimeType, data:Utilities.base64Encode(audioBytes)}}]}], generationConfig:{maxOutputTokens:65536, temperature:0.1}}),
muteHttpExceptions:true
});
return parseGeminiResponse(resp);
}
function transcribeViaFilesAPI(audioBytes, mimeType, fileName, apiKey, model, prompt) {
var startResp = UrlFetchApp.fetch("https://generativelanguage.googleapis.com/upload/v1beta/files?key="+apiKey, {
method:"POST", headers:{"X-Goog-Upload-Protocol":"resumable","X-Goog-Upload-Command":"start","X-Goog-Upload-Header-Content-Length":audioBytes.length.toString(),"X-Goog-Upload-Header-Content-Type":mimeType,"Content-Type":"application/json"},
payload:JSON.stringify({file:{display_name:fileName}}), muteHttpExceptions:true
});
var uploadUrl = startResp.getHeaders()["x-goog-upload-url"];
if (!uploadUrl) throw new Error("לא התקבל upload URL");
var uploadResp = UrlFetchApp.fetch(uploadUrl, {method:"POST", headers:{"X-Goog-Upload-Offset":"0","X-Goog-Upload-Command":"upload, finalize"}, payload:audioBytes, muteHttpExceptions:true});
var fileUri = JSON.parse(uploadResp.getContentText()).file.uri;
if (!fileUri) throw new Error("העלאה נכשלה");
Utilities.sleep(3000);
var genResp = UrlFetchApp.fetch("https://generativelanguage.googleapis.com/v1beta/models/"+model+":generateContent?key="+apiKey, {
method:"POST", contentType:"application/json",
payload:JSON.stringify({contents:[{parts:[{text:prompt},{file_data:{mime_type:mimeType, file_uri:fileUri}}]}], generationConfig:{maxOutputTokens:65536, temperature:0.1}}),
muteHttpExceptions:true
});
try { var fid = fileUri.split("/").pop(); UrlFetchApp.fetch("https://generativelanguage.googleapis.com/v1beta/files/"+fid+"?key="+apiKey, {method:"DELETE",muteHttpExceptions:true}); } catch(e){}
return parseGeminiResponse(genResp);
}
function transcribeAudio(attachment, apiKeys, prompt) {
var MAX_INLINE = 19 * 1024 * 1024;
Logger.log("[תמלול] 🎵 קובץ=" + attachment.getName() + " | גודל=" + Math.round(attachment.getBytes().length/1024) + "KB");
var audioBytes = attachment.getBytes(), rawMime = attachment.getContentType()||"";
var mimeType = (rawMime.startsWith("audio/")||rawMime==="video/mp4") ? rawMime : guessMimeType(attachment.getName());
var fileSize = audioBytes.length;
for (var ki = 0; ki < apiKeys.length; ki++) {
for (var mi = 0; mi < MODELS_TRANSCRIBE.length; mi++) {
try {
var text = fileSize > MAX_INLINE ? transcribeViaFilesAPI(audioBytes,mimeType,attachment.getName(),apiKeys[ki],MODELS_TRANSCRIBE[mi],prompt) : transcribeInline(audioBytes,mimeType,apiKeys[ki],MODELS_TRANSCRIBE[mi],prompt);
Logger.log("[תמלול] ✅ הצליח! מודל=" + MODELS_TRANSCRIBE[mi] + " | מפתח=..." + apiKeys[ki].slice(-6) + " | אורך=" + text.length + " תווים");
return {text:text, model:MODELS_TRANSCRIBE[mi]};
} catch(e) { Logger.log("[תמלול] ❌ נכשל | מודל=" + MODELS_TRANSCRIBE[mi] + " | מפתח=..." + apiKeys[ki].slice(-6) + " | שגיאה=" + e.message); }
}
}
throw new Error("כל המודלים נכשלו בתמלול");
}
// ═══════════════════════════════════════════════════════════════════════════════
// יצירת Word ועיצוב מייל
// ═══════════════════════════════════════════════════════════════════════════════
function createWordBlob(text, title) {
var htmlLines = ['
'];
text.split('\n').forEach(function(line) {
var t = line.trim();
if (!t) return;
if (/^# (.+)/.test(t)) { htmlLines.push('' + escHtml(t.replace(/^# /,'')) + '
'); return; }
if (/^## (.+)/.test(t)) { htmlLines.push('' + escHtml(t.replace(/^## /,'')) + '
'); return; }
if (/^### (.+)/.test(t)) { htmlLines.push('' + escHtml(t.replace(/^### /,'')) + '
'); return; }
var escaped = t.replace(/\*\*(.+?)\*\*/g, function(_, bold) { return '' + escHtml(bold) + ''; });
if (escaped === t) escaped = escHtml(t);
htmlLines.push('' + escaped + '
');
});
htmlLines.push('');
var htmlContent = htmlLines.join('\n');
var token = ScriptApp.getOAuthToken();
var metadata = JSON.stringify({name: title || 'תמלול', mimeType: 'application/vnd.google-apps.document'});
var boundary = 'boundary_word_' + Date.now();
var payload = '--' + boundary + '\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n' + metadata + '\r\n--' + boundary + '\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n' + htmlContent + '\r\n--' + boundary + '--';
var uploadResp = UrlFetchApp.fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
method:'POST', headers:{Authorization:'Bearer '+token,'Content-Type':'multipart/related; boundary='+boundary}, payload:payload, muteHttpExceptions:true
});
if (uploadResp.getResponseCode() !== 200) throw new Error('העלאה ל-Drive נכשלה');
var fileId = JSON.parse(uploadResp.getContentText()).id;
var exportResp = UrlFetchApp.fetch('https://www.googleapis.com/drive/v3/files/'+fileId+'/export?mimeType=application/vnd.openxmlformats-officedocument.wordprocessingml.document', {headers:{Authorization:'Bearer '+token}, muteHttpExceptions:true});
try { UrlFetchApp.fetch('https://www.googleapis.com/drive/v3/files/'+fileId, {method:'DELETE', headers:{Authorization:'Bearer '+token}, muteHttpExceptions:true}); } catch(e) {}
return exportResp.getBlob().setName((title||'תמלול') + '.docx');
}
function formatTranscript(text) {
if (!text) return "";
text = text.replace(/\*\*(.+?)\*\*/g, '$1');
text = text.replace(/###\s+(.+)$/gm, '$1
');
text = text.replace(/##\s+(.+)$/gm, '$1
');
var blocks = text.split(/\n{2,}/);
return blocks.map(function(p){p=p.trim();if(!p)return"";if(/^'+p.replace(/\n/g,'
')+'';}).filter(function(p){return p;}).join("\n");
}
function buildEmailHtml(content, modelName) {
var htmlContent = formatTranscript(content);
var displayModel = modelName ? modelName.replace(/^gemini-/i,"") : "";
var modelInfo = displayModel ? " | מודל: " + displayModel : "";
return '
'+htmlContent+'
נוצר בסייעתא דשמיא'+modelInfo+'
';
}
// ═══════════════════════════════════════════════════════════════════════════════
// ימות המשיח — הורדת הקלטות
// ═══════════════════════════════════════════════════════════════════════════════
function fetchYemotRecordings(settings) {
if (!settings.ymToken || !settings.ymExtension) return [];
Logger.log("[ימות] 🔍 בודק הקלטות | שלוחה=" + settings.ymExtension);
try {
var url = "https://www.call2all.co.il/ym/api/GetTextFile?token=" + settings.ymToken + "&path=ivr2:" + settings.ymExtension + "/";
var resp = UrlFetchApp.fetch(url, {muteHttpExceptions:true});
if (resp.getResponseCode() !== 200) { Logger.log("[ימות] ❌ שגיאה בחיבור"); return []; }
var data = JSON.parse(resp.getContentText());
if (!data.files || data.files.length === 0) { Logger.log("[ימות] אין קבצים חדשים"); return []; }
var audioFiles = data.files.filter(function(f) { return /\.(mp3|wav|m4a|ogg)$/i.test(f.name); });
Logger.log("[ימות] נמצאו " + audioFiles.length + " קבצי אודיו");
var results = [];
var props = PropertiesService.getScriptProperties();
audioFiles.forEach(function(file) {
var processedKey = "ym_" + file.name;
if (props.getProperty(processedKey)) return; // כבר עובד
try {
var fileUrl = "https://www.call2all.co.il/ym/api/DownloadFile?token=" + settings.ymToken + "&path=ivr2:" + settings.ymExtension + "/" + file.name;
var fileResp = UrlFetchApp.fetch(fileUrl, {muteHttpExceptions:true});
if (fileResp.getResponseCode() === 200) {
results.push({name: file.name, blob: fileResp.getBlob().setName(file.name)});
props.setProperty(processedKey, new Date().toISOString());
}
} catch(e) { Logger.log("[ימות] ❌ שגיאה בהורדת " + file.name + ": " + e.message); }
});
return results;
} catch(e) { Logger.log("[ימות] ❌ שגיאה: " + e.message); return []; }
}
// ═══════════════════════════════════════════════════════════════════════════════
// לוגיקה ראשית — בדיקת מיילים ותמלול
// ═══════════════════════════════════════════════════════════════════════════════
function הרצתתמלול() {
Logger.log("[תמלול] ▶️ מתחיל ריצה...");
var settings = getSettings();
if (settings.apiKeys.length === 0) { Logger.log("[תמלול] ❌ לא הוגדרו מפתחות API"); return; }
if (!settings.email) { Logger.log("[תמלול] ❌ לא הוגדר מייל יעד"); return; }
var prompt = getActivePrompt();
var runStart = Date.now();
var label = GmailApp.getUserLabelByName("תמלול") || GmailApp.createLabel("תמלול");
// ─── 1. בדיקת מיילים לפי נושא ──────────────────────────────────────────
if (settings.subjects.length > 0) {
var subjectQuery = settings.subjects.map(function(s){return 'subject:"'+s+'"';}).join(" OR ");
var query = 'is:unread -label:תמלול (' + subjectQuery + ')';
var threads = GmailApp.search(query, 0, 20);
Logger.log("[תמלול] נמצאו " + threads.length + " שרשורים לפי נושא");
for (var ti = 0; ti < threads.length; ti++) {
if (Date.now() - runStart > 240000) { Logger.log("[תמלול] ⏰ 4 דקות — עוצר"); break; }
var thread = threads[ti];
thread.addLabel(label);
if (hasAlreadyReplied(thread)) continue;
processThread(thread, settings, prompt, runStart);
}
}
// ─── 2. בדיקת מיילים מכתובות אוטומטיות ─────────────────────────────────
if (settings.autoEmails.length > 0) {
settings.autoEmails.forEach(function(addr) {
if (Date.now() - runStart > 240000) return;
var q = 'from:' + addr + ' is:unread -label:תמלול';
var autoThreads = GmailApp.search(q, 0, 30);
if (autoThreads.length > 0) Logger.log("[תמלול] נמצאו " + autoThreads.length + " מ-" + addr);
autoThreads.forEach(function(thread) {
if (Date.now() - runStart > 240000) return;
thread.addLabel(label);
if (hasAlreadyReplied(thread)) return;
processThread(thread, settings, prompt, runStart);
});
});
}
// ─── 3. הורדת הקלטות מימות המשיח ─────────────────────────────────────────
if (settings.ymToken && settings.ymExtension && Date.now() - runStart < 200000) {
var ymFiles = fetchYemotRecordings(settings);
if (ymFiles.length > 0) {
Logger.log("[ימות] 🎵 מתמלל " + ymFiles.length + " קבצים");
var ymResults = [];
ymFiles.forEach(function(file) {
if (Date.now() - runStart > 240000) return;
try {
var r = transcribeAudio(file.blob, settings.apiKeys, prompt);
ymResults.push({fileName:file.name, transcript:r.text, model:r.model, success:true});
} catch(e) { ymResults.push({fileName:file.name, success:false, error:e.message}); }
});
if (ymResults.length > 0) sendTranscriptionEmail(ymResults, settings, "תמלולי ימות המשיח");
}
}
Logger.log("[תמלול] ✅ ריצה הושלמה | סה\"כ=" + (Date.now()-runStart) + "ms");
}
function processThread(thread, settings, prompt, runStart) {
var messages = thread.getMessages();
for (var mi = 0; mi < messages.length; mi++) {
var message = messages[mi];
if (!message.isUnread()) continue;
if (Date.now() - runStart > 240000) break;
var audioAtts = (message.getAttachments()||[]).filter(isAudioFile);
if (audioAtts.length === 0) { message.markRead(); continue; }
var results = [];
for (var ai = 0; ai < audioAtts.length; ai++) {
if (Date.now() - runStart > 240000) break;
try { var r = transcribeAudio(audioAtts[ai], settings.apiKeys, prompt); results.push({fileName:audioAtts[ai].getName(),transcript:r.text,model:r.model,success:true}); }
catch(e) { results.push({fileName:audioAtts[ai].getName(),success:false,error:e.message}); }
}
if (results.length > 0) {
var emailContent = buildTranscribeReply(results);
var wordAtts = [];
results.forEach(function(r) {
if (r.success && r.transcript) {
try { wordAtts.push(createWordBlob(r.transcript, "תמלול — " + r.fileName.replace(/\.[^.]+$/,""))); } catch(e){}
}
});
var replyOpts = {htmlBody:emailContent.html, name:"תמלול"};
if (wordAtts.length > 0) replyOpts.attachments = wordAtts;
thread.reply(emailContent.plain, replyOpts);
}
message.markRead();
}
}
function buildTranscribeReply(results) {
var plain = "שלום,\n\nהתמלול מוכן:\n\n", htmlParts = [];
results.forEach(function(r, i) {
var lbl = "קובץ "+(i+1)+": "+r.fileName;
if (r.success) { plain += lbl+"\n"+r.transcript+"\n\n"; htmlParts.push('
'+escHtml(lbl)+'
'+formatTranscript(r.transcript)+'
'); }
else { plain += lbl+" — שגיאה: "+r.error+"\n\n"; htmlParts.push('
'+escHtml(lbl)+' — שגיאה: '+escHtml(r.error)+'
'); }
});
var model = results.find(function(r){return r.model;});
return {plain:plain, html:buildEmailHtml(htmlParts.join(""), model ? model.model : null)};
}
function sendTranscriptionEmail(results, settings, subject) {
var content = buildTranscribeReply(results);
var wordAtts = [];
results.forEach(function(r) {
if (r.success && r.transcript) {
try { wordAtts.push(createWordBlob(r.transcript, "תמלול — " + r.fileName.replace(/\.[^.]+$/,""))); } catch(e){}
}
});
var opts = {htmlBody:content.html, name:"תמלול"};
if (wordAtts.length > 0) opts.attachments = wordAtts;
GmailApp.sendEmail(settings.email, subject, content.plain, opts);
Logger.log("[תמלול] 📧 נשלח מייל ל-" + settings.email + " | " + results.length + " קבצים");
}
// ═══════════════════════════════════════════════════════════════════════════════
// טריגרים והפעלה
// ═══════════════════════════════════════════════════════════════════════════════
function הפעלהראשונה() {
// מחיקת טריגרים קיימים
ScriptApp.getProjectTriggers().forEach(function(t) { try { ScriptApp.deleteTrigger(t); } catch(e){} });
var settings = getSettings();
var minutes = settings.intervalMinutes || 5;
ScriptApp.newTrigger('הרצתתמלול').timeBased().everyMinutes(minutes).create();
Logger.log("✅ טריגר הופעל — ירוץ כל " + minutes + " דקות");
}
function כיבוי() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(function(t) { ScriptApp.deleteTrigger(t); });
Logger.log("🛑 " + triggers.length + " טריגרים נמחקו — המערכת כבויה");
}
// ═══════════════════════════════════════════════════════════════════════════════
// יצירת גליון ההגדרות — מעוצב
// ═══════════════════════════════════════════════════════════════════════════════
function יצירתגליון() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
if (!ss) {
// נסה למצוא גליון שמור
var id = PropertiesService.getScriptProperties().getProperty("SPREADSHEET_ID");
if (id) {
try { ss = SpreadsheetApp.openById(id); } catch(e) {}
}
}
if (!ss) {
ss = SpreadsheetApp.create("תמלול — הגדרות");
Logger.log("📄 נוצר גליון חדש: " + ss.getUrl());
}
// שמירת ID לשימוש עתידי (טריגרים)
PropertiesService.getScriptProperties().setProperty("SPREADSHEET_ID", ss.getId());
// ─── דף הגדרות ──────────────────────────────────────────────────────────
var existing = ss.getSheetByName("הגדרות");
if (existing) ss.deleteSheet(existing);
var sheet = ss.insertSheet("הגדרות");
sheet.setRightToLeft(true);
// כותרות
var headers = [
["הגדרה", "ערך", "הסבר"],
["── מפתחות API ──", "", ""],
["מפתח API 1", "", "מפתח Gemini API ראשי"],
["מפתח API 2", "", "מפתח גיבוי (אופציונלי)"],
["מפתח API 3", "", "מפתח נוסף (אופציונלי)"],
["מפתח API 4", "", "מפתח נוסף (אופציונלי)"],
["מפתח API 5", "", "מפתח נוסף (אופציונלי)"],
["── הגדרות מייל ──", "", ""],
["מייל יעד", "", "לאן ישלחו התמלולים"],
["נושאי מייל לתמלול", "תמלול", "נושאים מופרדים בפסיק (לדוג': תמלול,שיעור)"],
["כתובות אוטומטיות", "ivr@yml.li", "כתובות שכל מייל מהן יתומלל (מופרדות בפסיק)"],
["── ימות המשיח ──", "", ""],
["טוקן ימות המשיח", "", "הטוקן מחשבון ימות המשיח"],
["שלוחה", "", "מספר השלוחה עם ההקלטות (לדוג': 3)"],
["── הגדרות כלליות ──", "", ""],
["תדירות ריצה (דקות)", "5", "כל כמה דקות לבדוק (1-10)"]
];
sheet.getRange(1, 1, headers.length, 3).setValues(headers);
// עיצוב
sheet.setColumnWidth(1, 200);
sheet.setColumnWidth(2, 350);
sheet.setColumnWidth(3, 300);
// כותרת ראשית
var titleRow = sheet.getRange(1, 1, 1, 3);
titleRow.setBackground("#1a73e8").setFontColor("#fff").setFontWeight("bold").setFontSize(12);
// כותרות קטגוריות (שורות מפריד)
[2, 8, 11, 14].forEach(function(row) {
sheet.getRange(row, 1, 1, 3).setBackground("#e8f0fe").setFontWeight("bold").setFontColor("#1a73e8");
});
// עמודת הסבר — אפור
sheet.getRange(1, 3, headers.length, 1).setFontColor("#666").setFontSize(10);
// גבולות
sheet.getRange(1, 1, headers.length, 3).setBorder(true,true,true,true,true,true,"#d0d0d0",SpreadsheetApp.BorderStyle.SOLID);
// הגנה על עמודות A ו-C
var protection = sheet.protect().setDescription("הגנת הגדרות");
protection.setUnprotectedRanges([sheet.getRange("B3:B7"), sheet.getRange("B9:B10"), sheet.getRange("B12:B13"), sheet.getRange("B15")]);
// ─── דף הנחיות ──────────────────────────────────────────────────────────
var existingH = ss.getSheetByName("הנחיות");
if (existingH) ss.deleteSheet(existingH);
var hSheet = ss.insertSheet("הנחיות");
hSheet.setRightToLeft(true);
// כותרת
hSheet.getRange(1, 1).setValue("הנחיות התמלול (ניתן למחוק שורות — אי אפשר להוסיף או לשנות)");
hSheet.getRange(1, 1).setBackground("#1a73e8").setFontColor("#fff").setFontWeight("bold").setFontSize(12);
hSheet.setColumnWidth(1, 900);
// הנחיות — כל הנחיה בשורה נפרדת
var instructions = TRANSCRIBE_PROMPT.split("\n").filter(function(l){return l.trim();});
for (var i = 0; i < instructions.length; i++) {
hSheet.getRange(i + 2, 1).setValue(instructions[i]);
}
// עיצוב
hSheet.getRange(2, 1, instructions.length, 1).setFontSize(11).setWrap(true);
// הגנה — רק מחיקה מותרת (לא עריכה)
var hProtection = hSheet.protect().setDescription("הנחיות — מחיקה בלבד");
// הערה: ב-GAS אי אפשר להגביל לפעולת מחיקה בלבד, אז מגנים ונותנים למשתמש הנוכחי לערוך
hProtection.setWarningOnly(true);
// ─── דף ראשי ────────────────────────────────────────────────────────────
var mainExisting = ss.getSheetByName("ראשי");
if (mainExisting) ss.deleteSheet(mainExisting);
var mainSheet = ss.insertSheet("ראשי", 0);
mainSheet.setRightToLeft(true);
mainSheet.getRange("A1").setValue("🎵 מערכת תמלול אוטומטית").setFontSize(18).setFontWeight("bold").setFontColor("#1a73e8");
mainSheet.getRange("A3").setValue("📋 הוראות הפעלה:").setFontSize(13).setFontWeight("bold");
mainSheet.getRange("A4").setValue("1. מלא את ההגדרות בדף 'הגדרות' (מפתח API, מייל יעד, נושאים)");
mainSheet.getRange("A5").setValue("2. עבור לעורך הסקריפט: תפריט 'הרחבות' ← Apps Script");
mainSheet.getRange("A6").setValue("3. בעורך הסקריפט — בחר בפונקציה 'הפעלהראשונה' מהתפריט הנפתח למעלה ולחץ ▶️ הרצה");
mainSheet.getRange("A7").setValue("4. אשר את ההרשאות שיופיעו (גישה לג'ימייל, גיליונות, Drive)");
mainSheet.getRange("A8").setValue("5. זהו! המערכת תרוץ אוטומטית ברקע כל כמה דקות (לפי ההגדרה)");
mainSheet.getRange("A10").setValue("⚙️ פונקציות נוספות (הרצה מעורך הסקריפט):").setFontSize(12).setFontWeight("bold");
mainSheet.getRange("A11").setValue("• הרצתתמלול — הרצה ידנית חד-פעמית");
mainSheet.getRange("A12").setValue("• כיבוי — עצירת הריצה האוטומטית (מחיקת טריגרים)");
mainSheet.getRange("A14").setValue("💡 טיפ: אחרי ההפעלה הראשונה, יופיע תפריט '🎵 תמלול' בגליון — ממנו אפשר להפעיל הכל בקלות.").setFontColor("#666");
mainSheet.setColumnWidth(1, 700);
// ─── תפריט מותאם ─────────────────────────────────────────────────────────
try { createMenu(); } catch(e) { Logger.log("⚠️ התפריט יופיע כשתפתח את הגליון (onOpen)"); }
// מחיקת דפים מיותרים
ss.getSheets().forEach(function(s) {
var name = s.getName();
if (name !== "ראשי" && name !== "הגדרות" && name !== "הנחיות") {
try { ss.deleteSheet(s); } catch(e) {}
}
});
Logger.log("✅ גליון נוצר בהצלחה עם 3 דפים: ראשי, הגדרות, הנחיות");
Logger.log("🔗 קישור לגליון: " + ss.getUrl());
try { ss.toast("הגליון נוצר בהצלחה! מלא את ההגדרות והרץ 'הפעלה ראשונה'", "✅ מוכן", 10); } catch(e) {}
}
function createMenu() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('🎵 תמלול')
.addItem('▶️ הפעלה ראשונה (הגדרת טריגר)', 'הפעלהראשונה')
.addItem('🔄 הרץ תמלול עכשיו', 'הרצתתמלול')
.addSeparator()
.addItem('🛑 כיבוי (מחיקת טריגרים)', 'כיבוי')
.addSeparator()
.addItem('🔧 יצירת גליון מחדש', 'יצירתגליון')
.addToUi();
}
function onOpen() {
createMenu();
}