@@ -10,10 +10,8 @@ import {
1010 sendChatSessionMetrics ,
1111} from '@/content/messaging' ;
1212import { metricsManager } from './metrics/MetricsManager' ;
13- import {
14- POST_CONTENT_SELECTOR as POST_SELECTOR ,
15- ALLOWED_GROUP_NAMES as GROUPS ,
16- } from '@/content/dom/selectors' ;
13+ import { POST_CONTENT_SELECTOR as POST_SELECTOR } from '@/content/dom/selectors' ;
14+ import { isInAllowedGroupNow } from '@/content/utils/group' ;
1715import { ChatMessage } from '@/content/types' ;
1816// Chat session metrics state attached to chat window elements
1917interface ChatMetrics {
@@ -49,29 +47,14 @@ export class FacebookPostObserver {
4947 /** Selector for extracting post content */
5048 private readonly POST_CONTENT_SELECTOR = POST_SELECTOR ;
5149
52- /** List of allowed Facebook group names where the extension should work */
53- private readonly ALLOWED_GROUP_NAMES = GROUPS ;
54-
5550 /**
5651 * Checks if the current page is in an allowed Facebook group
5752 * @returns boolean indicating if the extension should be active
5853 */
5954 private isInAllowedGroup ( ) : boolean {
60- const currentGroupName = this . getCurrentGroupName ( ) ;
61-
62- if ( ! currentGroupName ) {
63- // Not in a group, extension should NOT work
64- return false ;
65- }
66-
67- const isAllowed = this . ALLOWED_GROUP_NAMES . some (
68- allowedName =>
69- currentGroupName . toLowerCase ( ) . includes ( allowedName . toLowerCase ( ) ) ||
70- allowedName . toLowerCase ( ) . includes ( currentGroupName . toLowerCase ( ) )
71- ) ;
72-
73- log ( `[AI-Slop] Group filtering - Current group: "${ currentGroupName } ", Allowed: ${ isAllowed } ` ) ;
74- return isAllowed ;
55+ const allowed = isInAllowedGroupNow ( ) ;
56+ log ( `[AI-Slop] Group filtering - Allowed: ${ allowed } ` ) ;
57+ return allowed ;
7558 }
7659
7760 /**
@@ -543,6 +526,11 @@ export class FacebookPostObserver {
543526 // Allow posts with no text content if they have media (media-only posts)
544527 if ( trimmedContent . length === 0 && ! hasMedia ) {
545528 log ( `[AI-Slop] ⏭️ Post ${ postId } has no content and no media, skipping analysis` ) ;
529+ metricsManager . trackEvent ( {
530+ type : 'post_skipped_empty' ,
531+ category : 'post' ,
532+ metadata : { postId } ,
533+ } ) ;
546534 return ;
547535 }
548536
@@ -562,6 +550,11 @@ export class FacebookPostObserver {
562550 log (
563551 `[AI-Slop] ⏭️ Post ${ postId } has repetitive text content (${ trimmedContent . length } chars, ${ facebookCount } "Facebook" occurrences), skipping analysis`
564552 ) ;
553+ metricsManager . trackEvent ( {
554+ type : 'post_skipped_repetitive' ,
555+ category : 'post' ,
556+ metadata : { postId, length : trimmedContent . length , facebookCount } ,
557+ } ) ;
565558 return ;
566559 }
567560 }
@@ -619,12 +612,88 @@ export class FacebookPostObserver {
619612
620613 // Store analysis result and inject icon
621614 this . injectFactCheckIcon ( postElement , postId , content , response ) ;
615+
616+ // Setup media/video tracking inside this post (if present)
617+ this . setupVideoTracking ( postElement , postId ) ;
622618 } catch ( error ) {
623619 logError ( `[AI-Slop] ❌ Error analyzing post ${ postId } :` , error ) ;
624620 // Don't show icon if analysis fails to avoid confusion
625621 }
626622 }
627623
624+ /**
625+ * Tracks video interactions inside a post
626+ */
627+ private videoState = new WeakMap < HTMLElement , { lastPlayTs : number | null ; playAccumMs : number ; lastProgressSentAt : number ; lastTime : number } > ( ) ;
628+
629+ private setupVideoTracking ( postElement : HTMLElement , postId : string ) : void {
630+ const videos = postElement . querySelectorAll ( 'video' ) ;
631+ videos . forEach ( v => {
632+ const el = v as HTMLVideoElement & { _aiSlopTracked ?: boolean } ;
633+ if ( el . _aiSlopTracked ) return ;
634+ el . _aiSlopTracked = true ;
635+
636+ this . videoState . set ( el , { lastPlayTs : null , playAccumMs : 0 , lastProgressSentAt : 0 , lastTime : 0 } ) ;
637+
638+ el . addEventListener ( 'play' , ( ) => {
639+ const st = this . videoState . get ( el ) ;
640+ if ( ! st ) return ;
641+ st . lastPlayTs = Date . now ( ) ;
642+ metricsManager . trackEvent ( {
643+ type : 'video_play' ,
644+ category : 'video' ,
645+ metadata : { postId, duration : Math . round ( el . duration || 0 ) } ,
646+ } ) ;
647+ } ) ;
648+
649+ el . addEventListener ( 'pause' , ( ) => {
650+ const st = this . videoState . get ( el ) ;
651+ if ( ! st ) return ;
652+ if ( st . lastPlayTs ) {
653+ const delta = Date . now ( ) - st . lastPlayTs ;
654+ st . playAccumMs += delta ;
655+ st . lastPlayTs = null ;
656+ metricsManager . trackEvent ( {
657+ type : 'video_pause' ,
658+ category : 'video' ,
659+ metadata : { postId, playSessionMs : delta , totalPlayedMs : st . playAccumMs , currentTime : Math . round ( el . currentTime ) } ,
660+ } ) ;
661+ }
662+ } ) ;
663+
664+ el . addEventListener ( 'ended' , ( ) => {
665+ const st = this . videoState . get ( el ) ;
666+ if ( ! st ) return ;
667+ if ( st . lastPlayTs ) {
668+ st . playAccumMs += Date . now ( ) - st . lastPlayTs ;
669+ st . lastPlayTs = null ;
670+ }
671+ const percent = el . duration ? Math . min ( 100 , Math . round ( ( el . currentTime / el . duration ) * 100 ) ) : 0 ;
672+ metricsManager . trackEvent ( {
673+ type : 'video_end' ,
674+ category : 'video' ,
675+ metadata : { postId, totalPlayedMs : st . playAccumMs , percentWatched : percent } ,
676+ } ) ;
677+ } ) ;
678+
679+ el . addEventListener ( 'timeupdate' , ( ) => {
680+ const st = this . videoState . get ( el ) ;
681+ if ( ! st ) return ;
682+ const now = Date . now ( ) ;
683+ if ( now - st . lastProgressSentAt > 5000 ) {
684+ st . lastProgressSentAt = now ;
685+ st . lastTime = el . currentTime ;
686+ const percent = el . duration ? Math . min ( 100 , Math . round ( ( el . currentTime / el . duration ) * 100 ) ) : 0 ;
687+ metricsManager . trackEvent ( {
688+ type : 'video_progress' ,
689+ category : 'video' ,
690+ metadata : { postId, currentTime : Math . round ( el . currentTime ) , percent } ,
691+ } ) ;
692+ }
693+ } ) ;
694+ } ) ;
695+ }
696+
628697 /**
629698 * Generates a unique ID for a post based on Facebook's URL structure
630699 * Priority: URL-based ID > content-based fallback > DOM-based fallback
@@ -1266,6 +1335,11 @@ export class FacebookPostObserver {
12661335 try {
12671336 targetElement . appendChild ( iconContainer ) ;
12681337 log ( `[AI-Slop] ✅ Icon injected successfully for post ${ postId } with consistent positioning` ) ;
1338+ metricsManager . trackEvent ( {
1339+ type : 'icon_injected' ,
1340+ category : 'interaction' ,
1341+ metadata : { postId } ,
1342+ } ) ;
12691343 } catch ( error ) {
12701344 logError ( `[AI-Slop] ❌ Failed to append icon for post ${ postId } :` , error ) ;
12711345
@@ -1280,6 +1354,7 @@ export class FacebookPostObserver {
12801354 }
12811355 container . appendChild ( iconContainer ) ;
12821356 log ( `[AI-Slop] ✅ Icon injected using fallback container` ) ;
1357+ metricsManager . trackEvent ( { type : 'icon_injected_fallback' , category : 'interaction' , metadata : { postId } } ) ;
12831358 break ;
12841359 } catch {
12851360 continue ;
@@ -1337,6 +1412,7 @@ export class FacebookPostObserver {
13371412 }
13381413
13391414 this . showChatOverlay ( postElement , postId , content , analysisResult ) ;
1415+ metricsManager . trackEvent ( { type : 'chat_overlay_open' , category : 'chat' , metadata : { postId } } ) ;
13401416 }
13411417
13421418 /**
@@ -1627,6 +1703,7 @@ export class FacebookPostObserver {
16271703
16281704 // Close chat window fast; send metrics in background
16291705 closeButton ?. addEventListener ( 'click' , ( ) => {
1706+ metricsManager . trackEvent ( { type : 'chat_overlay_close' , category : 'chat' } ) ;
16301707 const metrics =
16311708 ( chatWindow as unknown as HTMLElement & { _chatMetrics ?: ChatMetrics } ) . _chatMetrics ||
16321709 null ;
@@ -1648,6 +1725,12 @@ export class FacebookPostObserver {
16481725
16491726 // Minimize chat window
16501727 minimizeButton ?. addEventListener ( 'click' , ( ) => {
1728+ const minimized = chatWindow . classList . contains ( 'minimized' ) ;
1729+ metricsManager . trackEvent ( {
1730+ type : 'chat_overlay_minimize_toggle' ,
1731+ category : 'chat' ,
1732+ label : minimized ? 'restore' : 'minimize' ,
1733+ } ) ;
16511734 if ( chatWindow . classList . contains ( 'minimized' ) ) {
16521735 chatWindow . classList . remove ( 'minimized' ) ;
16531736 } else {
@@ -1664,6 +1747,12 @@ export class FacebookPostObserver {
16641747 } ;
16651748
16661749 sendButton ?. addEventListener ( 'click' , sendMessage ) ;
1750+ chatInput ?. addEventListener ( 'focus' , ( ) => {
1751+ metricsManager . trackEvent ( { type : 'chat_input_focus' , category : 'chat' } ) ;
1752+ } ) ;
1753+ chatInput ?. addEventListener ( 'blur' , ( ) => {
1754+ metricsManager . trackEvent ( { type : 'chat_input_blur' , category : 'chat' } ) ;
1755+ } ) ;
16671756 chatInput ?. addEventListener ( 'keypress' , e => {
16681757 if ( e . key === 'Enter' ) {
16691758 sendMessage ( ) ;
@@ -1672,6 +1761,7 @@ export class FacebookPostObserver {
16721761
16731762 // Ignore analysis
16741763 ignoreButton ?. addEventListener ( 'click' , ( ) => {
1764+ metricsManager . trackEvent ( { type : 'chat_ignore_analysis' , category : 'chat' } ) ;
16751765 const analysisResult = chatWindow . querySelector ( '.analysis-result' ) ;
16761766 if ( analysisResult ) {
16771767 analysisResult . innerHTML = `
@@ -1960,6 +2050,8 @@ export class FacebookPostObserver {
19602050 // Add class for potential drag-specific styles
19612051 chatWindow . classList . add ( 'dragging' ) ;
19622052
2053+ metricsManager . trackEvent ( { type : 'chat_overlay_drag_start' , category : 'chat' } ) ;
2054+
19632055 e . preventDefault ( ) ; // Prevent default browser drag behavior
19642056 }
19652057 } ;
@@ -1983,6 +2075,12 @@ export class FacebookPostObserver {
19832075 chatWindow . style . userSelect = '' ;
19842076 chatWindow . classList . remove ( 'dragging' ) ;
19852077
2078+ metricsManager . trackEvent ( {
2079+ type : 'chat_overlay_drag_end' ,
2080+ category : 'chat' ,
2081+ metadata : { x : xOffset , y : yOffset } ,
2082+ } ) ;
2083+
19862084 // Cancel any pending animation frame
19872085 if ( animationId ) {
19882086 cancelAnimationFrame ( animationId ) ;
0 commit comments