Monitors Telegram channels, identifies AI & automation updates, and rewrites them for business owners
Size
52.7 KB
Version
1.1.1
Created
Feb 4, 2026
Updated
14 days ago
1// ==UserScript==
2// @name Telegram AI Content Curator for Business Owners
3// @description Monitors Telegram channels, identifies AI & automation updates, and rewrites them for business owners
4// @version 1.1.1
5// @match https://*.web.telegram.org/*
6// @icon https://web.telegram.org/a/favicon-unread.svg
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.deleteValue
10// @grant GM.listValues
11// ==/UserScript==
12(function() {
13 'use strict';
14
15 console.log('🚀 Telegram AI Content Curator initialized');
16
17 // ===== UTILITY FUNCTIONS =====
18
19 function debounce(func, wait) {
20 let timeout;
21 return function executedFunction(...args) {
22 const later = () => {
23 clearTimeout(timeout);
24 func(...args);
25 };
26 clearTimeout(timeout);
27 timeout = setTimeout(later, wait);
28 };
29 }
30
31 // ===== DATA MANAGEMENT =====
32
33 async function saveUpdate(update) {
34 const updates = await GM.getValue('telegram_updates', []);
35 updates.unshift(update);
36 await GM.setValue('telegram_updates', updates);
37 console.log('💾 Update saved:', update.id);
38 return update;
39 }
40
41 async function getUpdates() {
42 return await GM.getValue('telegram_updates', []);
43 }
44
45 async function updateStatus(updateId, newStatus) {
46 const updates = await GM.getValue('telegram_updates', []);
47 const update = updates.find(u => u.id === updateId);
48 if (update) {
49 update.status = newStatus;
50 update.statusChangedAt = new Date().toISOString();
51 await GM.setValue('telegram_updates', updates);
52 console.log(`✅ Status updated for ${updateId}: ${newStatus}`);
53 }
54 }
55
56 async function deleteUpdate(updateId) {
57 const updates = await GM.getValue('telegram_updates', []);
58 const filtered = updates.filter(u => u.id !== updateId);
59 await GM.setValue('telegram_updates', filtered);
60 console.log(`🗑️ Update deleted: ${updateId}`);
61 }
62
63 async function updateEditedContent(updateId, editedContent) {
64 const updates = await GM.getValue('telegram_updates', []);
65 const update = updates.find(u => u.id === updateId);
66 if (update) {
67 update.editedContent = editedContent;
68 update.lastEditedAt = new Date().toISOString();
69 await GM.setValue('telegram_updates', updates);
70 console.log(`✏️ Content edited for ${updateId}`);
71 }
72 }
73
74 // ===== AI PROCESSING =====
75
76 async function classifyContent(messageText, channelName) {
77 console.log('🤖 Classifying content with AI...');
78
79 try {
80 const prompt = `אני צריכה שתסווג את ההודעה הבאה מערוץ טלגרם "${channelName}".
81
82ההודעה:
83"""
84${messageText}
85"""
86
87סווג את ההודעה לפי הקריטריונים הבאים:
88
89**כלול רק אם מתקיים לפחות אחד:**
90- הצגת כלי AI או אוטומציה חדש
91- שיפור משמעותי לכלי קיים
92- דוגמה לשימוש עסקי ב-AI
93- אוטומציה שחוסכת זמן, כסף או כוח אדם
94- חיבור בין AI לתהליכים עסקיים יומיומיים
95
96**פסול אם מתקיים אחד מהבאים:**
97- תוכן טכני למפתחים בלבד
98- קוד, API או ארכיטקטורה ללא הקשר עסקי
99- חדשות כלליות ללא שימוש מעשי
100- ספקולציות, שמועות או הייפ
101- תוכן שאינו רלוונטי לעסקים קטנים ובינוניים
102
103החזר את התשובה בפורמט JSON.`;
104
105 const classification = await RM.aiCall(prompt, {
106 type: 'json_schema',
107 json_schema: {
108 name: 'content_classification',
109 schema: {
110 type: 'object',
111 properties: {
112 isRelevant: {
113 type: 'boolean',
114 description: 'האם התוכן רלוונטי לבעלות העסק'
115 },
116 relevanceReason: {
117 type: 'string',
118 description: 'הסבר קצר למה התוכן רלוונטי או לא'
119 },
120 category: {
121 type: 'string',
122 enum: ['כלי חדש', 'שיפור כלי קיים', 'דוגמה עסקית', 'אוטומציה', 'חיבור AI לעסק', 'לא רלוונטי']
123 },
124 businessValue: {
125 type: 'string',
126 enum: ['גבוה', 'בינוני', 'נמוך']
127 }
128 },
129 required: ['isRelevant', 'relevanceReason', 'category', 'businessValue']
130 }
131 }
132 });
133
134 console.log('✅ Classification result:', classification);
135 return classification;
136 } catch (error) {
137 console.error('❌ AI classification failed:', error);
138 return {
139 isRelevant: false,
140 relevanceReason: 'שגיאה בסיווג',
141 category: 'לא רלוונטי',
142 businessValue: 'נמוך'
143 };
144 }
145 }
146
147 async function analyzeBusinessValue(messageText, classification) {
148 console.log('🤖 Analyzing business value with AI...');
149
150 try {
151 const prompt = `נתחה את הערך העסקי של העדכון הבא:
152
153"""
154${messageText}
155"""
156
157קטגוריה: ${classification.category}
158רמת ערך: ${classification.businessValue}
159
160ספק ניתוח מפורט:`;
161
162 const analysis = await RM.aiCall(prompt, {
163 type: 'json_schema',
164 json_schema: {
165 name: 'business_value_analysis',
166 schema: {
167 type: 'object',
168 properties: {
169 mainInnovation: {
170 type: 'string',
171 description: 'מה החידוש המרכזי'
172 },
173 targetAudience: {
174 type: 'string',
175 description: 'למי זה רלוונטי כבעלת עסק'
176 },
177 businessProcess: {
178 type: 'string',
179 description: 'איזה תהליך עסקי זה משפר'
180 },
181 practicalValue: {
182 type: 'string',
183 description: 'מה הערך המעשי והיישומי'
184 },
185 keyBenefits: {
186 type: 'array',
187 items: { type: 'string' },
188 description: '3-5 נקודות בולטות של ערך עסקי',
189 minItems: 3,
190 maxItems: 5
191 }
192 },
193 required: ['mainInnovation', 'targetAudience', 'businessProcess', 'practicalValue', 'keyBenefits']
194 }
195 }
196 });
197
198 console.log('✅ Business value analysis:', analysis);
199 return analysis;
200 } catch (error) {
201 console.error('❌ Business value analysis failed:', error);
202 return null;
203 }
204 }
205
206 async function rewriteForBusinessOwners(messageText, analysis) {
207 console.log('🤖 Rewriting content for business owners...');
208
209 try {
210 const prompt = `שכתב את התוכן הבא לקהילת בעלות עסקים:
211
212תוכן מקורי:
213"""
214${messageText}
215"""
216
217ניתוח ערך עסקי:
218- חידוש מרכזי: ${analysis.mainInnovation}
219- קהל יעד: ${analysis.targetAudience}
220- תהליך עסקי: ${analysis.businessProcess}
221- ערך מעשי: ${analysis.practicalValue}
222
223כללי כתיבה:
224- שפה פשוטה וברורה
225- ללא מונחים טכניים אלא אם הכרחי
226- פנייה לבעלות עסקים
227- מיקוד בתוצאה ולא בטכנולוגיה
228- טון קהילתי, נגיש ומעורר סקרנות
229
230הטקסט חייב לענות על:
231- למה זה חשוב לי כבעלת עסק
232- איך אני יכולה להשתמש בזה בפועל
233- מה יוצא לי מזה עכשיו
234
235צור תוכן מעובד:`;
236
237 const rewritten = await RM.aiCall(prompt, {
238 type: 'json_schema',
239 json_schema: {
240 name: 'rewritten_content',
241 schema: {
242 type: 'object',
243 properties: {
244 communityPost: {
245 type: 'string',
246 description: 'תקציר קצר בגובה פוסט קהילה'
247 },
248 telegramMessage: {
249 type: 'string',
250 description: 'ניסוח קצר שמתאים להודעת טלגרם או סטורי'
251 },
252 callToAction: {
253 type: 'string',
254 description: 'שאלה או קריאה לפעולה שמעודדת דיון בקהילה'
255 }
256 },
257 required: ['communityPost', 'telegramMessage', 'callToAction']
258 }
259 }
260 });
261
262 console.log('✅ Content rewritten:', rewritten);
263 return rewritten;
264 } catch (error) {
265 console.error('❌ Content rewriting failed:', error);
266 return null;
267 }
268 }
269
270 async function processMessage(messageText, channelName, messageUrl) {
271 console.log('🔄 Processing new message...');
272
273 // Step 1: Classify
274 const classification = await classifyContent(messageText, channelName);
275
276 if (!classification.isRelevant) {
277 console.log('⏭️ Message not relevant, skipping');
278 return null;
279 }
280
281 // Step 2: Analyze business value
282 const analysis = await analyzeBusinessValue(messageText, classification);
283
284 if (!analysis) {
285 console.log('⚠️ Failed to analyze business value');
286 return null;
287 }
288
289 // Step 3: Rewrite content
290 const rewritten = await rewriteForBusinessOwners(messageText, analysis);
291
292 if (!rewritten) {
293 console.log('⚠️ Failed to rewrite content');
294 return null;
295 }
296
297 // Step 4: Create update object
298 const update = {
299 id: `update_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
300 originalText: messageText,
301 channelName: channelName,
302 messageUrl: messageUrl,
303 classification: classification,
304 analysis: analysis,
305 rewritten: rewritten,
306 status: 'new',
307 priority: classification.businessValue === 'גבוה' ? 'high' : 'normal',
308 createdAt: new Date().toISOString(),
309 statusChangedAt: new Date().toISOString()
310 };
311
312 // Step 5: Save
313 await saveUpdate(update);
314
315 console.log('✅ Message processed successfully');
316 return update;
317 }
318
319 // ===== UI COMPONENTS =====
320
321 function createMainUI() {
322 const container = document.createElement('div');
323 container.id = 'telegram-curator-panel';
324 container.innerHTML = `
325 <div class="curator-header">
326 <h2>🤖 AI Content Curator</h2>
327 <div class="curator-actions">
328 <button id="curator-refresh-btn" class="curator-btn curator-btn-primary">
329 🔄 רענן
330 </button>
331 <button id="curator-toggle-btn" class="curator-btn curator-btn-secondary">
332 ➖ מזער
333 </button>
334 </div>
335 </div>
336 <div class="curator-filters">
337 <button class="curator-filter-btn active" data-filter="all">הכל</button>
338 <button class="curator-filter-btn" data-filter="new">חדש</button>
339 <button class="curator-filter-btn" data-filter="approved">אושר</button>
340 <button class="curator-filter-btn" data-filter="rejected">נדחה</button>
341 <button class="curator-filter-btn" data-filter="high">עדיפות גבוהה</button>
342 </div>
343 <div id="curator-updates-list" class="curator-updates-list">
344 <div class="curator-loading">טוען עדכונים...</div>
345 </div>
346 `;
347
348 // Add styles
349 const styles = `
350 #telegram-curator-panel {
351 position: fixed;
352 top: 20px;
353 left: 20px;
354 width: 450px;
355 max-height: 80vh;
356 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
357 border-radius: 16px;
358 box-shadow: 0 20px 60px rgba(0,0,0,0.3);
359 z-index: 999999;
360 display: flex;
361 flex-direction: column;
362 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
363 overflow: hidden;
364 transition: all 0.3s ease;
365 }
366
367 #telegram-curator-panel.minimized {
368 max-height: 60px;
369 }
370
371 #telegram-curator-panel.minimized .curator-filters,
372 #telegram-curator-panel.minimized .curator-updates-list {
373 display: none;
374 }
375
376 .curator-header {
377 padding: 16px 20px;
378 background: rgba(255,255,255,0.1);
379 backdrop-filter: blur(10px);
380 display: flex;
381 justify-content: space-between;
382 align-items: center;
383 border-bottom: 1px solid rgba(255,255,255,0.2);
384 }
385
386 .curator-header h2 {
387 margin: 0;
388 color: white;
389 font-size: 18px;
390 font-weight: 600;
391 }
392
393 .curator-actions {
394 display: flex;
395 gap: 8px;
396 }
397
398 .curator-btn {
399 padding: 8px 16px;
400 border: none;
401 border-radius: 8px;
402 cursor: pointer;
403 font-size: 14px;
404 font-weight: 500;
405 transition: all 0.2s;
406 }
407
408 .curator-btn-primary {
409 background: white;
410 color: #667eea;
411 }
412
413 .curator-btn-primary:hover {
414 transform: translateY(-2px);
415 box-shadow: 0 4px 12px rgba(255,255,255,0.3);
416 }
417
418 .curator-btn-secondary {
419 background: rgba(255,255,255,0.2);
420 color: white;
421 }
422
423 .curator-btn-secondary:hover {
424 background: rgba(255,255,255,0.3);
425 }
426
427 .curator-filters {
428 padding: 12px 20px;
429 display: flex;
430 gap: 8px;
431 flex-wrap: wrap;
432 background: rgba(255,255,255,0.05);
433 }
434
435 .curator-filter-btn {
436 padding: 6px 14px;
437 border: 1px solid rgba(255,255,255,0.3);
438 background: transparent;
439 color: white;
440 border-radius: 20px;
441 cursor: pointer;
442 font-size: 13px;
443 transition: all 0.2s;
444 }
445
446 .curator-filter-btn:hover {
447 background: rgba(255,255,255,0.1);
448 }
449
450 .curator-filter-btn.active {
451 background: white;
452 color: #667eea;
453 border-color: white;
454 }
455
456 .curator-updates-list {
457 flex: 1;
458 overflow-y: auto;
459 padding: 16px;
460 }
461
462 .curator-loading {
463 text-align: center;
464 color: white;
465 padding: 40px 20px;
466 font-size: 14px;
467 }
468
469 .curator-update-card {
470 background: white;
471 border-radius: 12px;
472 padding: 16px;
473 margin-bottom: 12px;
474 box-shadow: 0 4px 12px rgba(0,0,0,0.1);
475 transition: all 0.2s;
476 }
477
478 .curator-update-card:hover {
479 transform: translateY(-2px);
480 box-shadow: 0 8px 20px rgba(0,0,0,0.15);
481 }
482
483 .curator-update-header {
484 display: flex;
485 justify-content: space-between;
486 align-items: flex-start;
487 margin-bottom: 12px;
488 }
489
490 .curator-update-meta {
491 flex: 1;
492 }
493
494 .curator-channel-name {
495 font-weight: 600;
496 color: #667eea;
497 font-size: 14px;
498 margin-bottom: 4px;
499 }
500
501 .curator-update-time {
502 font-size: 12px;
503 color: #999;
504 }
505
506 .curator-badges {
507 display: flex;
508 gap: 6px;
509 flex-wrap: wrap;
510 }
511
512 .curator-badge {
513 padding: 4px 10px;
514 border-radius: 12px;
515 font-size: 11px;
516 font-weight: 500;
517 }
518
519 .curator-badge-new {
520 background: #4ade80;
521 color: white;
522 }
523
524 .curator-badge-approved {
525 background: #3b82f6;
526 color: white;
527 }
528
529 .curator-badge-rejected {
530 background: #ef4444;
531 color: white;
532 }
533
534 .curator-badge-high {
535 background: #f59e0b;
536 color: white;
537 }
538
539 .curator-badge-category {
540 background: #e5e7eb;
541 color: #374151;
542 }
543
544 .curator-content-section {
545 margin: 12px 0;
546 }
547
548 .curator-section-title {
549 font-size: 12px;
550 font-weight: 600;
551 color: #6b7280;
552 margin-bottom: 6px;
553 text-transform: uppercase;
554 }
555
556 .curator-original-text {
557 font-size: 13px;
558 color: #4b5563;
559 background: #f9fafb;
560 padding: 10px;
561 border-radius: 8px;
562 border-right: 3px solid #667eea;
563 max-height: 80px;
564 overflow-y: auto;
565 line-height: 1.5;
566 }
567
568 .curator-rewritten-text {
569 font-size: 14px;
570 color: #1f2937;
571 background: #f0fdf4;
572 padding: 12px;
573 border-radius: 8px;
574 border-right: 3px solid #4ade80;
575 line-height: 1.6;
576 white-space: pre-wrap;
577 }
578
579 .curator-benefits-list {
580 list-style: none;
581 padding: 0;
582 margin: 8px 0;
583 }
584
585 .curator-benefits-list li {
586 padding: 6px 0;
587 font-size: 13px;
588 color: #374151;
589 display: flex;
590 align-items: flex-start;
591 }
592
593 .curator-benefits-list li:before {
594 content: "✓";
595 color: #4ade80;
596 font-weight: bold;
597 margin-left: 8px;
598 flex-shrink: 0;
599 }
600
601 .curator-update-actions {
602 display: flex;
603 gap: 8px;
604 margin-top: 12px;
605 padding-top: 12px;
606 border-top: 1px solid #e5e7eb;
607 }
608
609 .curator-action-btn {
610 flex: 1;
611 padding: 8px 12px;
612 border: none;
613 border-radius: 8px;
614 cursor: pointer;
615 font-size: 13px;
616 font-weight: 500;
617 transition: all 0.2s;
618 }
619
620 .curator-action-btn-approve {
621 background: #3b82f6;
622 color: white;
623 }
624
625 .curator-action-btn-approve:hover {
626 background: #2563eb;
627 }
628
629 .curator-action-btn-reject {
630 background: #ef4444;
631 color: white;
632 }
633
634 .curator-action-btn-reject:hover {
635 background: #dc2626;
636 }
637
638 .curator-action-btn-copy {
639 background: #10b981;
640 color: white;
641 }
642
643 .curator-action-btn-copy:hover {
644 background: #059669;
645 }
646
647 .curator-action-btn-edit {
648 background: #f59e0b;
649 color: white;
650 }
651
652 .curator-action-btn-edit:hover {
653 background: #d97706;
654 }
655
656 .curator-action-btn-delete {
657 background: #6b7280;
658 color: white;
659 }
660
661 .curator-action-btn-delete:hover {
662 background: #4b5563;
663 }
664
665 .curator-empty-state {
666 text-align: center;
667 padding: 40px 20px;
668 color: white;
669 }
670
671 .curator-empty-state-icon {
672 font-size: 48px;
673 margin-bottom: 16px;
674 }
675
676 .curator-empty-state-text {
677 font-size: 16px;
678 margin-bottom: 8px;
679 }
680
681 .curator-empty-state-subtext {
682 font-size: 13px;
683 opacity: 0.8;
684 }
685
686 .curator-edit-modal {
687 position: fixed;
688 top: 0;
689 left: 0;
690 right: 0;
691 bottom: 0;
692 background: rgba(0,0,0,0.7);
693 display: flex;
694 align-items: center;
695 justify-content: center;
696 z-index: 1000000;
697 }
698
699 .curator-edit-content {
700 background: white;
701 border-radius: 16px;
702 padding: 24px;
703 width: 90%;
704 max-width: 600px;
705 max-height: 80vh;
706 overflow-y: auto;
707 }
708
709 .curator-edit-header {
710 display: flex;
711 justify-content: space-between;
712 align-items: center;
713 margin-bottom: 20px;
714 }
715
716 .curator-edit-title {
717 font-size: 20px;
718 font-weight: 600;
719 color: #1f2937;
720 }
721
722 .curator-edit-close {
723 background: none;
724 border: none;
725 font-size: 24px;
726 cursor: pointer;
727 color: #6b7280;
728 }
729
730 .curator-edit-textarea {
731 width: 100%;
732 min-height: 200px;
733 padding: 12px;
734 border: 2px solid #e5e7eb;
735 border-radius: 8px;
736 font-size: 14px;
737 font-family: inherit;
738 resize: vertical;
739 margin-bottom: 16px;
740 }
741
742 .curator-edit-textarea:focus {
743 outline: none;
744 border-color: #667eea;
745 }
746
747 .curator-edit-actions {
748 display: flex;
749 gap: 12px;
750 justify-content: flex-end;
751 }
752
753 .curator-updates-list::-webkit-scrollbar {
754 width: 8px;
755 }
756
757 .curator-updates-list::-webkit-scrollbar-track {
758 background: rgba(255,255,255,0.1);
759 border-radius: 4px;
760 }
761
762 .curator-updates-list::-webkit-scrollbar-thumb {
763 background: rgba(255,255,255,0.3);
764 border-radius: 4px;
765 }
766
767 .curator-updates-list::-webkit-scrollbar-thumb:hover {
768 background: rgba(255,255,255,0.5);
769 }
770
771 /* Hover Tooltip Styles */
772 .curator-hover-tooltip {
773 position: fixed;
774 background: white;
775 border-radius: 12px;
776 box-shadow: 0 10px 40px rgba(0,0,0,0.2);
777 z-index: 1000000;
778 max-width: 400px;
779 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
780 animation: tooltipFadeIn 0.2s ease;
781 }
782
783 @keyframes tooltipFadeIn {
784 from {
785 opacity: 0;
786 transform: translateY(-10px);
787 }
788 to {
789 opacity: 1;
790 transform: translateY(0);
791 }
792 }
793
794 .curator-tooltip-loading {
795 padding: 20px;
796 text-align: center;
797 }
798
799 .curator-tooltip-header {
800 display: flex;
801 align-items: center;
802 gap: 8px;
803 padding: 16px;
804 border-bottom: 1px solid #e5e7eb;
805 }
806
807 .curator-tooltip-icon {
808 font-size: 20px;
809 }
810
811 .curator-tooltip-title {
812 font-weight: 600;
813 color: #1f2937;
814 font-size: 15px;
815 flex: 1;
816 }
817
818 .curator-tooltip-badge {
819 background: #667eea;
820 color: white;
821 padding: 4px 10px;
822 border-radius: 12px;
823 font-size: 11px;
824 font-weight: 500;
825 }
826
827 .curator-tooltip-loader {
828 padding: 20px;
829 text-align: center;
830 }
831
832 .curator-loader-spinner {
833 width: 40px;
834 height: 40px;
835 border: 3px solid #e5e7eb;
836 border-top-color: #667eea;
837 border-radius: 50%;
838 animation: spin 0.8s linear infinite;
839 margin: 0 auto 12px;
840 }
841
842 @keyframes spin {
843 to { transform: rotate(360deg); }
844 }
845
846 .curator-tooltip-loader p {
847 margin: 0;
848 color: #6b7280;
849 font-size: 13px;
850 }
851
852 .curator-tooltip-not-relevant {
853 padding: 16px;
854 }
855
856 .curator-tooltip-reason {
857 margin: 12px 0 0 0;
858 color: #6b7280;
859 font-size: 13px;
860 line-height: 1.5;
861 }
862
863 .curator-tooltip-relevant {
864 max-height: 500px;
865 overflow-y: auto;
866 }
867
868 .curator-tooltip-content {
869 padding: 16px;
870 }
871
872 .curator-tooltip-section {
873 margin-bottom: 16px;
874 }
875
876 .curator-tooltip-section:last-child {
877 margin-bottom: 0;
878 }
879
880 .curator-tooltip-section strong {
881 display: block;
882 color: #374151;
883 font-size: 12px;
884 margin-bottom: 8px;
885 }
886
887 .curator-tooltip-text {
888 margin: 0;
889 color: #1f2937;
890 font-size: 14px;
891 line-height: 1.6;
892 background: #f0fdf4;
893 padding: 12px;
894 border-radius: 8px;
895 border-right: 3px solid #4ade80;
896 }
897
898 .curator-tooltip-benefits {
899 list-style: none;
900 padding: 0;
901 margin: 0;
902 }
903
904 .curator-tooltip-benefits li {
905 padding: 6px 0;
906 font-size: 13px;
907 color: #374151;
908 display: flex;
909 align-items: flex-start;
910 line-height: 1.5;
911 }
912
913 .curator-tooltip-benefits li:before {
914 content: "✓";
915 color: #4ade80;
916 font-weight: bold;
917 margin-left: 8px;
918 flex-shrink: 0;
919 }
920
921 .curator-tooltip-actions {
922 display: flex;
923 gap: 8px;
924 padding: 12px 16px;
925 border-top: 1px solid #e5e7eb;
926 background: #f9fafb;
927 border-radius: 0 0 12px 12px;
928 }
929
930 .curator-tooltip-btn {
931 flex: 1;
932 padding: 8px 12px;
933 border: none;
934 border-radius: 8px;
935 cursor: pointer;
936 font-size: 12px;
937 font-weight: 500;
938 transition: all 0.2s;
939 }
940
941 .curator-tooltip-btn-save {
942 background: #667eea;
943 color: white;
944 }
945
946 .curator-tooltip-btn-save:hover {
947 background: #5568d3;
948 }
949
950 .curator-tooltip-btn-copy {
951 background: #10b981;
952 color: white;
953 }
954
955 .curator-tooltip-btn-copy:hover {
956 background: #059669;
957 }
958
959 .curator-tooltip-btn-close {
960 background: #e5e7eb;
961 color: #6b7280;
962 flex: 0 0 auto;
963 padding: 8px 12px;
964 }
965
966 .curator-tooltip-btn-close:hover {
967 background: #d1d5db;
968 }
969
970 /* Notification Styles */
971 .curator-notification {
972 position: fixed;
973 top: 20px;
974 right: 20px;
975 background: #10b981;
976 color: white;
977 padding: 12px 20px;
978 border-radius: 8px;
979 box-shadow: 0 10px 30px rgba(0,0,0,0.2);
980 z-index: 1000001;
981 font-size: 14px;
982 font-weight: 500;
983 opacity: 0;
984 transform: translateY(-20px);
985 transition: all 0.3s ease;
986 }
987
988 .curator-notification-show {
989 opacity: 1;
990 transform: translateY(0);
991 }
992 `;
993
994 const styleSheet = document.createElement('style');
995 styleSheet.textContent = styles;
996 document.head.appendChild(styleSheet);
997
998 return container;
999 }
1000
1001 function createUpdateCard(update) {
1002 const card = document.createElement('div');
1003 card.className = 'curator-update-card';
1004 card.dataset.updateId = update.id;
1005 card.dataset.status = update.status;
1006 card.dataset.priority = update.priority;
1007
1008 const statusBadge = update.status === 'new' ? 'חדש' :
1009 update.status === 'approved' ? 'אושר' :
1010 update.status === 'rejected' ? 'נדחה' : update.status;
1011
1012 const statusClass = `curator-badge-${update.status}`;
1013
1014 const benefits = update.analysis?.keyBenefits || [];
1015 const benefitsHTML = benefits.map(b => `<li>${b}</li>`).join('');
1016
1017 card.innerHTML = `
1018 <div class="curator-update-header">
1019 <div class="curator-update-meta">
1020 <div class="curator-channel-name">${update.channelName}</div>
1021 <div class="curator-update-time">${new Date(update.createdAt).toLocaleString('he-IL')}</div>
1022 </div>
1023 <div class="curator-badges">
1024 <span class="curator-badge ${statusClass}">${statusBadge}</span>
1025 ${update.priority === 'high' ? '<span class="curator-badge curator-badge-high">עדיפות גבוהה</span>' : ''}
1026 <span class="curator-badge curator-badge-category">${update.classification.category}</span>
1027 </div>
1028 </div>
1029
1030 <div class="curator-content-section">
1031 <div class="curator-section-title">📝 טקסט מקורי</div>
1032 <div class="curator-original-text">${update.originalText}</div>
1033 </div>
1034
1035 ${update.analysis ? `
1036 <div class="curator-content-section">
1037 <div class="curator-section-title">💡 ערך עסקי</div>
1038 <ul class="curator-benefits-list">
1039 ${benefitsHTML}
1040 </ul>
1041 </div>
1042 ` : ''}
1043
1044 ${update.rewritten ? `
1045 <div class="curator-content-section">
1046 <div class="curator-section-title">✨ תוכן מעובד</div>
1047 <div class="curator-rewritten-text">${update.editedContent || update.rewritten.communityPost}</div>
1048 </div>
1049 ` : ''}
1050
1051 <div class="curator-update-actions">
1052 ${update.status === 'new' ? `
1053 <button class="curator-action-btn curator-action-btn-approve" data-action="approve">
1054 ✓ אשר
1055 </button>
1056 <button class="curator-action-btn curator-action-btn-reject" data-action="reject">
1057 ✗ דחה
1058 </button>
1059 ` : ''}
1060 <button class="curator-action-btn curator-action-btn-copy" data-action="copy">
1061 📋 העתק
1062 </button>
1063 <button class="curator-action-btn curator-action-btn-edit" data-action="edit">
1064 ✏️ ערוך
1065 </button>
1066 <button class="curator-action-btn curator-action-btn-delete" data-action="delete">
1067 🗑️ מחק
1068 </button>
1069 </div>
1070 `;
1071
1072 // Add event listeners
1073 card.querySelectorAll('[data-action]').forEach(btn => {
1074 btn.addEventListener('click', async (e) => {
1075 const action = e.target.dataset.action;
1076 await handleUpdateAction(update.id, action, update);
1077 });
1078 });
1079
1080 return card;
1081 }
1082
1083 async function handleUpdateAction(updateId, action, update) {
1084 console.log(`🎬 Action: ${action} for update ${updateId}`);
1085
1086 switch(action) {
1087 case 'approve':
1088 await updateStatus(updateId, 'approved');
1089 await refreshUI();
1090 break;
1091
1092 case 'reject':
1093 await updateStatus(updateId, 'rejected');
1094 await refreshUI();
1095 break;
1096
1097 case 'copy':
1098 const textToCopy = update.editedContent || update.rewritten?.communityPost || update.originalText;
1099 await GM.setClipboard(textToCopy);
1100 alert('✅ הטקסט הועתק ללוח');
1101 break;
1102
1103 case 'edit':
1104 showEditModal(update);
1105 break;
1106
1107 case 'delete':
1108 if (confirm('האם למחוק עדכון זה?')) {
1109 await deleteUpdate(updateId);
1110 await refreshUI();
1111 }
1112 break;
1113 }
1114 }
1115
1116 function showEditModal(update) {
1117 const modal = document.createElement('div');
1118 modal.className = 'curator-edit-modal';
1119
1120 const currentText = update.editedContent || update.rewritten?.communityPost || '';
1121
1122 modal.innerHTML = `
1123 <div class="curator-edit-content">
1124 <div class="curator-edit-header">
1125 <h3 class="curator-edit-title">✏️ עריכת תוכן</h3>
1126 <button class="curator-edit-close">✕</button>
1127 </div>
1128 <textarea class="curator-edit-textarea" placeholder="ערוך את התוכן כאן...">${currentText}</textarea>
1129 <div class="curator-edit-actions">
1130 <button class="curator-action-btn curator-action-btn-approve" id="save-edit-btn">
1131 💾 שמור
1132 </button>
1133 <button class="curator-action-btn curator-action-btn-delete" id="cancel-edit-btn">
1134 ✕ ביטול
1135 </button>
1136 </div>
1137 </div>
1138 `;
1139
1140 document.body.appendChild(modal);
1141
1142 const closeModal = () => modal.remove();
1143
1144 modal.querySelector('.curator-edit-close').addEventListener('click', closeModal);
1145 modal.querySelector('#cancel-edit-btn').addEventListener('click', closeModal);
1146
1147 modal.querySelector('#save-edit-btn').addEventListener('click', async () => {
1148 const newText = modal.querySelector('.curator-edit-textarea').value;
1149 await updateEditedContent(update.id, newText);
1150 closeModal();
1151 await refreshUI();
1152 });
1153
1154 modal.addEventListener('click', (e) => {
1155 if (e.target === modal) closeModal();
1156 });
1157 }
1158
1159 async function refreshUI(filter = 'all') {
1160 const listContainer = document.getElementById('curator-updates-list');
1161 if (!listContainer) return;
1162
1163 listContainer.innerHTML = '<div class="curator-loading">טוען עדכונים...</div>';
1164
1165 const updates = await getUpdates();
1166
1167 let filteredUpdates = updates;
1168
1169 if (filter === 'new') {
1170 filteredUpdates = updates.filter(u => u.status === 'new');
1171 } else if (filter === 'approved') {
1172 filteredUpdates = updates.filter(u => u.status === 'approved');
1173 } else if (filter === 'rejected') {
1174 filteredUpdates = updates.filter(u => u.status === 'rejected');
1175 } else if (filter === 'high') {
1176 filteredUpdates = updates.filter(u => u.priority === 'high');
1177 }
1178
1179 listContainer.innerHTML = '';
1180
1181 if (filteredUpdates.length === 0) {
1182 listContainer.innerHTML = `
1183 <div class="curator-empty-state">
1184 <div class="curator-empty-state-icon">📭</div>
1185 <div class="curator-empty-state-text">אין עדכונים להצגה</div>
1186 <div class="curator-empty-state-subtext">עדכונים חדשים יופיעו כאן אוטומטית</div>
1187 </div>
1188 `;
1189 } else {
1190 filteredUpdates.forEach(update => {
1191 const card = createUpdateCard(update);
1192 listContainer.appendChild(card);
1193 });
1194 }
1195
1196 console.log(`✅ UI refreshed with ${filteredUpdates.length} updates`);
1197 }
1198
1199 // ===== MESSAGE MONITORING =====
1200
1201 let currentTooltip = null;
1202 let processingCache = new Map();
1203
1204 function removeTooltip() {
1205 if (currentTooltip) {
1206 currentTooltip.remove();
1207 currentTooltip = null;
1208 }
1209 }
1210
1211 function createLoadingTooltip(messageElement) {
1212 removeTooltip();
1213
1214 const tooltip = document.createElement('div');
1215 tooltip.className = 'curator-hover-tooltip curator-tooltip-loading';
1216 tooltip.innerHTML = `
1217 <div class="curator-tooltip-header">
1218 <span class="curator-tooltip-icon">🤖</span>
1219 <span class="curator-tooltip-title">מעבד הודעה...</span>
1220 </div>
1221 <div class="curator-tooltip-loader">
1222 <div class="curator-loader-spinner"></div>
1223 <p>AI מנתח את התוכן</p>
1224 </div>
1225 `;
1226
1227 positionTooltip(tooltip, messageElement);
1228 document.body.appendChild(tooltip);
1229 currentTooltip = tooltip;
1230
1231 return tooltip;
1232 }
1233
1234 function createContentTooltip(messageElement, result) {
1235 removeTooltip();
1236
1237 if (!result.isRelevant) {
1238 const tooltip = document.createElement('div');
1239 tooltip.className = 'curator-hover-tooltip curator-tooltip-not-relevant';
1240 tooltip.innerHTML = `
1241 <div class="curator-tooltip-header">
1242 <span class="curator-tooltip-icon">⏭️</span>
1243 <span class="curator-tooltip-title">לא רלוונטי</span>
1244 </div>
1245 <p class="curator-tooltip-reason">${result.classification.relevanceReason}</p>
1246 `;
1247
1248 positionTooltip(tooltip, messageElement);
1249 document.body.appendChild(tooltip);
1250 currentTooltip = tooltip;
1251 return;
1252 }
1253
1254 const tooltip = document.createElement('div');
1255 tooltip.className = 'curator-hover-tooltip curator-tooltip-relevant';
1256 tooltip.innerHTML = `
1257 <div class="curator-tooltip-header">
1258 <span class="curator-tooltip-icon">✨</span>
1259 <span class="curator-tooltip-title">תוכן רלוונטי!</span>
1260 <span class="curator-tooltip-badge">${result.classification.category}</span>
1261 </div>
1262
1263 <div class="curator-tooltip-content">
1264 <div class="curator-tooltip-section">
1265 <strong>📝 ניסוח לקהילה:</strong>
1266 <p class="curator-tooltip-text">${result.rewritten.communityPost}</p>
1267 </div>
1268
1269 <div class="curator-tooltip-section">
1270 <strong>💡 ערך עסקי:</strong>
1271 <ul class="curator-tooltip-benefits">
1272 ${result.analysis.keyBenefits.slice(0, 3).map(b => `<li>${b}</li>`).join('')}
1273 </ul>
1274 </div>
1275 </div>
1276
1277 <div class="curator-tooltip-actions">
1278 <button class="curator-tooltip-btn curator-tooltip-btn-save" data-action="save">
1279 💾 שמור לפאנל
1280 </button>
1281 <button class="curator-tooltip-btn curator-tooltip-btn-copy" data-action="copy">
1282 📋 העתק
1283 </button>
1284 <button class="curator-tooltip-btn curator-tooltip-btn-close" data-action="close">
1285 ✕
1286 </button>
1287 </div>
1288 `;
1289
1290 positionTooltip(tooltip, messageElement);
1291 document.body.appendChild(tooltip);
1292 currentTooltip = tooltip;
1293
1294 // Add event listeners
1295 tooltip.querySelector('[data-action="save"]').addEventListener('click', async () => {
1296 await saveUpdate(result.update);
1297 await refreshUI();
1298 removeTooltip();
1299 showNotification('✅ נשמר בהצלחה!');
1300 });
1301
1302 tooltip.querySelector('[data-action="copy"]').addEventListener('click', async () => {
1303 await GM.setClipboard(result.rewritten.communityPost);
1304 showNotification('✅ הועתק ללוח!');
1305 });
1306
1307 tooltip.querySelector('[data-action="close"]').addEventListener('click', () => {
1308 removeTooltip();
1309 });
1310 }
1311
1312 function positionTooltip(tooltip, messageElement) {
1313 const rect = messageElement.getBoundingClientRect();
1314 const tooltipWidth = 400;
1315 const tooltipMaxHeight = 500;
1316
1317 // Position to the left of the message
1318 let left = rect.left - tooltipWidth - 20;
1319 let top = rect.top;
1320
1321 // If not enough space on the left, position on the right
1322 if (left < 20) {
1323 left = rect.right + 20;
1324 }
1325
1326 // If not enough space on the right either, position below
1327 if (left + tooltipWidth > window.innerWidth - 20) {
1328 left = rect.left;
1329 top = rect.bottom + 10;
1330 }
1331
1332 // Make sure it doesn't go off screen vertically
1333 if (top + tooltipMaxHeight > window.innerHeight) {
1334 top = window.innerHeight - tooltipMaxHeight - 20;
1335 }
1336
1337 if (top < 20) {
1338 top = 20;
1339 }
1340
1341 tooltip.style.left = `${left}px`;
1342 tooltip.style.top = `${top}px`;
1343 }
1344
1345 function showNotification(message) {
1346 const notification = document.createElement('div');
1347 notification.className = 'curator-notification';
1348 notification.textContent = message;
1349 document.body.appendChild(notification);
1350
1351 setTimeout(() => {
1352 notification.classList.add('curator-notification-show');
1353 }, 10);
1354
1355 setTimeout(() => {
1356 notification.classList.remove('curator-notification-show');
1357 setTimeout(() => notification.remove(), 300);
1358 }, 2000);
1359 }
1360
1361 async function processMessageOnHover(messageElement, messageText, channelName, messageUrl) {
1362 const cacheKey = `${channelName}_${messageText.substring(0, 100)}`;
1363
1364 // Check cache first
1365 if (processingCache.has(cacheKey)) {
1366 const cached = processingCache.get(cacheKey);
1367 createContentTooltip(messageElement, cached);
1368 return;
1369 }
1370
1371 // Show loading tooltip
1372 createLoadingTooltip(messageElement);
1373
1374 try {
1375 // Step 1: Classify
1376 const classification = await classifyContent(messageText, channelName);
1377
1378 if (!classification.isRelevant) {
1379 const result = { isRelevant: false, classification };
1380 processingCache.set(cacheKey, result);
1381 createContentTooltip(messageElement, result);
1382 return;
1383 }
1384
1385 // Step 2: Analyze business value
1386 const analysis = await analyzeBusinessValue(messageText, classification);
1387
1388 if (!analysis) {
1389 const result = { isRelevant: false, classification: { relevanceReason: 'שגיאה בניתוח' } };
1390 createContentTooltip(messageElement, result);
1391 return;
1392 }
1393
1394 // Step 3: Rewrite content
1395 const rewritten = await rewriteForBusinessOwners(messageText, analysis);
1396
1397 if (!rewritten) {
1398 const result = { isRelevant: false, classification: { relevanceReason: 'שגיאה בניסוח' } };
1399 createContentTooltip(messageElement, result);
1400 return;
1401 }
1402
1403 // Create update object (but don't save yet)
1404 const update = {
1405 id: `update_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
1406 originalText: messageText,
1407 channelName: channelName,
1408 messageUrl: messageUrl,
1409 classification: classification,
1410 analysis: analysis,
1411 rewritten: rewritten,
1412 status: 'new',
1413 priority: classification.businessValue === 'גבוה' ? 'high' : 'normal',
1414 createdAt: new Date().toISOString(),
1415 statusChangedAt: new Date().toISOString()
1416 };
1417
1418 const result = {
1419 isRelevant: true,
1420 classification,
1421 analysis,
1422 rewritten,
1423 update
1424 };
1425
1426 processingCache.set(cacheKey, result);
1427 createContentTooltip(messageElement, result);
1428
1429 } catch (error) {
1430 console.error('❌ Error processing message on hover:', error);
1431 removeTooltip();
1432 }
1433 }
1434
1435 function extractMessageData(messageElement) {
1436 try {
1437 // Extract message text
1438 const textElement = messageElement.querySelector('.text-content, .message-content, [class*="text"], [class*="message"]');
1439 const messageText = textElement ? textElement.textContent.trim() : '';
1440
1441 if (!messageText || messageText.length < 50) {
1442 return null;
1443 }
1444
1445 // Extract channel name
1446 const channelElement = document.querySelector('.chat-info-title, .peer-title, [class*="title"]');
1447 const channelName = channelElement ? channelElement.textContent.trim() : 'Unknown Channel';
1448
1449 // Create message URL
1450 const messageUrl = window.location.href;
1451
1452 return {
1453 messageText,
1454 channelName,
1455 messageUrl
1456 };
1457 } catch (error) {
1458 console.error('❌ Error extracting message data:', error);
1459 return null;
1460 }
1461 }
1462
1463 const processedMessages = new Set();
1464 let hoverTimeout = null;
1465
1466 async function monitorMessages() {
1467 const messages = document.querySelectorAll('.message, [class*="Message"], .bubble');
1468
1469 for (const message of messages) {
1470 const messageId = message.getAttribute('data-message-id') || message.id;
1471
1472 if (!messageId || processedMessages.has(messageId)) {
1473 continue;
1474 }
1475
1476 processedMessages.add(messageId);
1477
1478 // Add hover listeners
1479 message.addEventListener('mouseenter', () => {
1480 clearTimeout(hoverTimeout);
1481 hoverTimeout = setTimeout(() => {
1482 const data = extractMessageData(message);
1483 if (data) {
1484 processMessageOnHover(message, data.messageText, data.channelName, data.messageUrl);
1485 }
1486 }, 500); // Wait 500ms before processing
1487 });
1488
1489 message.addEventListener('mouseleave', () => {
1490 clearTimeout(hoverTimeout);
1491 // Don't remove tooltip immediately - let user interact with it
1492 setTimeout(() => {
1493 if (currentTooltip && !currentTooltip.matches(':hover')) {
1494 removeTooltip();
1495 }
1496 }, 300);
1497 });
1498 }
1499
1500 // Keep tooltip open when hovering over it
1501 if (currentTooltip) {
1502 currentTooltip.addEventListener('mouseleave', () => {
1503 setTimeout(() => {
1504 if (currentTooltip && !currentTooltip.matches(':hover')) {
1505 removeTooltip();
1506 }
1507 }, 300);
1508 });
1509 }
1510 }
1511
1512 const debouncedMonitor = debounce(monitorMessages, 1000);
1513
1514 function setupMessageObserver() {
1515 const observer = new MutationObserver(debouncedMonitor);
1516
1517 const targetNode = document.querySelector('.messages-container, #column-center, [class*="messages"]') || document.body;
1518
1519 observer.observe(targetNode, {
1520 childList: true,
1521 subtree: true
1522 });
1523
1524 console.log('👀 Message observer started');
1525 }
1526
1527 // ===== INITIALIZATION =====
1528
1529 async function init() {
1530 console.log('🚀 Initializing Telegram AI Content Curator...');
1531
1532 // Wait for page to load
1533 await new Promise(resolve => {
1534 if (document.readyState === 'complete') {
1535 resolve();
1536 } else {
1537 window.addEventListener('load', resolve);
1538 }
1539 });
1540
1541 // Create UI
1542 const ui = createMainUI();
1543 document.body.appendChild(ui);
1544
1545 // Setup event listeners
1546 document.getElementById('curator-refresh-btn').addEventListener('click', () => {
1547 const activeFilter = document.querySelector('.curator-filter-btn.active');
1548 const filter = activeFilter ? activeFilter.dataset.filter : 'all';
1549 refreshUI(filter);
1550 });
1551
1552 document.getElementById('curator-toggle-btn').addEventListener('click', () => {
1553 ui.classList.toggle('minimized');
1554 const btn = document.getElementById('curator-toggle-btn');
1555 btn.textContent = ui.classList.contains('minimized') ? '➕ הרחב' : '➖ מזער';
1556 });
1557
1558 document.querySelectorAll('.curator-filter-btn').forEach(btn => {
1559 btn.addEventListener('click', (e) => {
1560 document.querySelectorAll('.curator-filter-btn').forEach(b => b.classList.remove('active'));
1561 e.target.classList.add('active');
1562 refreshUI(e.target.dataset.filter);
1563 });
1564 });
1565
1566 // Initial UI load
1567 await refreshUI();
1568
1569 // Start monitoring messages
1570 setupMessageObserver();
1571
1572 console.log('✅ Telegram AI Content Curator ready!');
1573 }
1574
1575 // Start the extension
1576 init();
1577
1578})();