@@ -495,9 +495,115 @@ async function sendToHomeAssistant(options) {
495495 } ;
496496 } else {
497497 // Direct send scenario - get page info via scripting
498+ // We need to inject a self-contained function since chrome.scripting.executeScript
499+ // serializes the function and external dependencies won't be available
498500 const results = await chrome . scripting . executeScript ( {
499501 target : { tabId : tab . id } ,
500- func : createPageInfo ,
502+ func : ( ) => {
503+ // Self-contained getFavicon implementation
504+ function getFavicon ( ) {
505+ const links = document . getElementsByTagName ( 'link' ) ;
506+
507+ const formatPriority = {
508+ 'png' : 1 ,
509+ 'jpg' : 2 ,
510+ 'jpeg' : 2 ,
511+ 'webp' : 3 ,
512+ 'ico' : 4 ,
513+ 'svg' : 10 ,
514+ } ;
515+
516+ const candidates = [ ] ;
517+
518+ for ( let i = 0 ; i < links . length ; i ++ ) {
519+ const rel = links [ i ] . rel ;
520+ const href = links [ i ] . href ;
521+
522+ if ( rel && rel . toLowerCase ( ) . includes ( 'icon' ) && href && ! rel . toLowerCase ( ) . includes ( 'apple' ) ) {
523+ if ( href . startsWith ( 'file://' ) ) {
524+ continue ;
525+ }
526+
527+ let format = '' ;
528+ const typeAttr = links [ i ] . type ;
529+ if ( typeAttr ) {
530+ const match = typeAttr . match ( / i m a g e \/ ( [ a - z A - Z 0 - 9 - ] + ) / ) ;
531+ if ( match ) {
532+ format = match [ 1 ] . toLowerCase ( ) ;
533+ }
534+ } else {
535+ const urlMatch = href . match ( / \. ( \w + ) ( \? .* ) ? $ / ) ;
536+ if ( urlMatch ) {
537+ format = urlMatch [ 1 ] . toLowerCase ( ) ;
538+ }
539+ }
540+
541+ if ( format === 'x-icon' ) {
542+ format = 'ico' ;
543+ }
544+
545+ let size = 0 ;
546+ if ( links [ i ] . sizes && links [ i ] . sizes . value ) {
547+ const sizes = links [ i ] . sizes . value . split ( ' ' ) ;
548+ for ( const s of sizes ) {
549+ if ( s === 'any' ) { continue ; }
550+ const parts = s . split ( 'x' ) ;
551+ if ( parts . length === 2 ) {
552+ const n = parseInt ( parts [ 0 ] , 10 ) ;
553+ if ( n > size ) {
554+ size = n ;
555+ }
556+ }
557+ }
558+ }
559+
560+ if ( ! size ) {
561+ size = 16 ;
562+ }
563+
564+ const formatScore = formatPriority [ format ] || 10 ;
565+ const sizeScore = Math . max ( 0 , 100 - size / 10 ) ;
566+ const totalScore = formatScore * 1000 + sizeScore ;
567+
568+ candidates . push ( {
569+ href,
570+ format,
571+ size,
572+ score : totalScore ,
573+ } ) ;
574+ }
575+ }
576+
577+ if ( candidates . length > 0 ) {
578+ candidates . sort ( ( a , b ) => a . score - b . score ) ;
579+ return candidates [ 0 ] . href ;
580+ }
581+
582+ if ( location . origin && ! location . protocol . startsWith ( 'file' ) ) {
583+ return location . origin + '/favicon.ico' ;
584+ }
585+
586+ return '' ;
587+ }
588+
589+ // Self-contained getSelectedText implementation
590+ function getSelectedText ( ) {
591+ if ( window . getSelection ) {
592+ return window . getSelection ( ) . toString ( ) ;
593+ }
594+ return '' ;
595+ }
596+
597+ // Create and return page info
598+ return {
599+ title : document . title ,
600+ url : window . location . href ,
601+ favicon : getFavicon ( ) ,
602+ selected : getSelectedText ( ) ,
603+ timestamp : new Date ( ) . toISOString ( ) ,
604+ user_agent : navigator . userAgent ,
605+ } ;
606+ } ,
501607 } ) ;
502608
503609 if ( ! results || ! results [ 0 ] || ! results [ 0 ] . result ) {
@@ -506,6 +612,11 @@ async function sendToHomeAssistant(options) {
506612
507613 pageInfo = results [ 0 ] . result ;
508614
615+ // Use extension icon as fallback if favicon is empty
616+ if ( ! pageInfo . favicon ) {
617+ pageInfo . favicon = chrome . runtime . getURL ( 'icon-256.png' ) ;
618+ }
619+
509620 // Validate favicon URL
510621 pageInfo . favicon = await validateFaviconUrl ( pageInfo . favicon ) ;
511622 }
0 commit comments