2023-07-25 06:17:46 +00:00
const currentURL = new URL ( document . location ) ;
2023-08-07 06:43:23 +00:00
let hiddenWikisRevealed = { } ;
2023-07-25 06:17:46 +00:00
2024-01-26 11:10:23 +00:00
function base64Decode ( text ) {
text = text . replace ( /\s+/g , '' ) . replace ( /\-/g , '+' ) . replace ( /\_/g , '/' ) ;
2024-02-08 08:34:53 +00:00
return decodeURIComponent ( Array . prototype . map . call ( atob ( text ) , function ( c ) { return '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) ; } ) . join ( '' ) ) ;
2024-01-26 11:10:23 +00:00
}
2023-07-25 06:17:46 +00:00
// Function to create an observer to watch for mutations on search pages
2023-11-06 02:25:47 +00:00
// This is used for search engines that paginate via JavaScript,
// or overwrite their results and remove IWB's elements
2024-04-23 05:18:42 +00:00
function addDOMChangeObserver ( callback , searchEngine , storage ) {
2023-07-25 06:17:46 +00:00
const config = {
attributes : false ,
childList : true ,
subtree : true
}
2024-04-23 05:18:42 +00:00
const wrappedCallback = ( mutations , observer ) => {
callback ( searchEngine , storage , mutations , observer ) ;
} ;
const domObserver = new MutationObserver ( wrappedCallback ) ;
2024-02-18 06:41:06 +00:00
domObserver . observe ( document . body , config ) ;
2023-07-25 06:17:46 +00:00
}
2023-08-04 05:50:12 +00:00
function insertCSS ( ) {
// Output CSS
styleString = `
. iwb - notice {
2023-08-07 06:43:23 +00:00
display : block ! important ;
2023-08-04 05:50:12 +00:00
margin : . 5 em . 5 em 1 em . 5 em ! important ;
padding : . 5 em . 5 em . 5 em 1 em ! important ;
border - left : 3 px solid # FFCC33 ! important ;
font - size : 14 px ! important ;
color : white ! important ;
mix - blend - mode : difference ! important ;
}
. iwb - notice a {
text - decoration : underline ! important ;
color : white ! important ;
mix - blend - mode : difference ! important ;
}
. iwb - notice button {
cursor : pointer ! important ;
2023-08-07 06:43:23 +00:00
display : inline - block ! important ;
2023-08-04 05:50:12 +00:00
padding : 2 px 8 px ! important ;
2023-08-07 06:43:23 +00:00
margin : 8 px 8 px 0 0 ! important ;
2023-08-04 05:50:12 +00:00
background - color : transparent ! important ;
2023-08-07 06:43:23 +00:00
border : 1 px solid ! important ;
2023-08-04 05:50:12 +00:00
border - radius : 5 px ! important ;
2023-09-18 07:41:42 +00:00
font - size : 12 px ! important ;
2023-08-04 05:50:12 +00:00
color : white ! important ;
mix - blend - mode : difference ! important ;
2023-08-07 06:43:23 +00:00
text - align : left ! important ;
}
2023-08-04 05:50:12 +00:00
. iwb - hide {
display : none ! important ;
}
. iwb - show {
display : block ! important ;
}
. iwb - notice button : hover {
outline : 1 px solid ! important ;
}
2023-11-03 09:20:45 +00:00
. iwb - new - link - container {
2023-10-16 05:47:02 +00:00
display : inline - block ;
font - size : 12 px ! important ;
text - decoration : none ;
padding - left : 5 px ;
position : relative ;
}
2023-11-03 09:20:45 +00:00
. iwb - new - link {
display : inline - block ;
text - decoration : none ;
position : relative ;
}
2023-10-16 05:47:02 +00:00
. iwb - new - link : hover {
text - decoration : none ;
z - index : 9999999 ;
}
. iwb - new - link button {
cursor : pointer ;
color : white ;
background : # 005799 ;
border : 0 px solid # fff ;
border - radius : 10 px ;
padding : 5 px 10 px ;
2023-11-02 08:35:34 +00:00
margin : . 5 em 0 ;
2023-10-16 05:47:02 +00:00
font - size : 1.2 em ;
width : fit - content ;
}
. iwb - new - link button : hover {
background : # 00467 a ;
}
. iwb - new - link button * {
vertical - align : middle ;
}
. iwb - new - link div : first - of - type {
display : inline - block ;
background : white ;
border - radius : 16 px ;
margin - right : 10 px ;
width : fit - content ;
height : fit - content ;
line - height : 12 px ;
padding : 5 px ;
}
. iwb - new - link img {
width : 12 px ;
}
2024-02-12 08:04:20 +00:00
. iwb - disavow > * : not ( [ class ^= "iwb-" ] ) : not ( . scs _child _rpr ) {
2023-10-16 05:47:02 +00:00
opacity : 60 % ;
pointer - events : none ;
cursor : default ;
}
2024-02-12 08:04:20 +00:00
. iwb - disavow > a : not ( [ class ^= "iwb-" ] ) : not ( . scs _child _rpr ) ,
. iwb - disavow > * : not ( [ class ^= "iwb-" ] ) : not ( . scs _child _rpr ) a ,
. iwb - disavow > * : not ( [ class ^= "iwb-" ] ) : not ( . scs _child _rpr ) a * {
2023-10-16 05:47:02 +00:00
text - decoration : line - through ! important ;
}
2023-11-02 08:35:34 +00:00
2023-11-03 09:20:45 +00:00
. iwb - result - controls {
display : inline - block ;
margin : . 5 em ;
2023-11-02 08:35:34 +00:00
font - size : 12 px ;
color : white ! important ;
mix - blend - mode : difference ! important ;
}
2023-11-03 09:20:45 +00:00
. iwb - result - controls > div {
2023-11-02 08:35:34 +00:00
display : inline - block ;
2023-11-03 09:20:45 +00:00
cursor : pointer ;
text - decoration : underline ;
text - decoration - style : dotted ;
text - decoration - thickness : 1 px ;
padding : 0 1 em ;
}
. iwb - notice . iwb - result - controls {
margin : 8 px 0 0 0 ;
}
2023-08-04 05:50:12 +00:00
`
style = document . createElement ( 'style' ) ;
2023-11-02 13:58:25 +00:00
style . classList . add ( 'iwb-styles' ) ;
2023-08-04 05:50:12 +00:00
style . textContent = styleString ;
document . head . append ( style ) ;
}
// Function to convert strings to consistent IDs
// Used to convert wiki names to element IDs
function stringToId ( string ) {
2023-08-31 05:16:15 +00:00
return string . replaceAll ( ' ' , '-' ) . replaceAll ( "'" , '' ) . replace ( /\W/g , '' ) . toLowerCase ( ) ;
2023-08-04 05:50:12 +00:00
}
2023-08-08 06:33:23 +00:00
// Function to escape string to use in regex
function escapeRegex ( string ) {
return string . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ;
}
2024-03-13 08:01:41 +00:00
function removeSubstringIfAtEnd ( str , sub ) {
if ( sub && str . endsWith ( sub ) ) {
return str . slice ( 0 , - sub . length ) ;
}
return str ;
}
2023-11-02 08:35:34 +00:00
function replaceSearchResults ( searchResultContainer , site , link ) {
2024-01-11 08:53:30 +00:00
let originArticle = commonFunctionGetOriginArticle ( link , site ) ;
let destinationArticle = commonFunctionGetDestinationArticle ( site , originArticle ) ;
let newURL = commonFunctionGetNewURL ( link , site ) ;
2023-10-16 05:47:02 +00:00
2023-11-06 00:59:24 +00:00
if ( searchResultContainer && ! searchResultContainer . querySelector ( '.iwb-new-link' ) ) {
if ( ! searchResultContainer . classList . contains ( 'iwb-detected' ) ) {
searchResultContainer . classList . add ( 'iwb-detected' ) ;
searchResultContainer . classList . add ( 'iwb-disavow' ) ;
}
2023-11-06 02:25:47 +00:00
// Using aside to avoid conflicts with website CSS and listeners:
2023-11-06 00:59:24 +00:00
let indieContainer = document . createElement ( 'aside' ) ;
2023-11-03 09:20:45 +00:00
indieContainer . classList . add ( 'iwb-new-link-container' ) ;
let indieResultLink = document . createElement ( 'a' ) ;
2023-10-16 05:47:02 +00:00
indieResultLink . href = newURL ;
indieResultLink . classList . add ( 'iwb-new-link' ) ;
2023-11-03 09:20:45 +00:00
let indieResultButton = document . createElement ( 'button' ) ;
let indieResultFaviconContainer = document . createElement ( 'div' ) ;
let indieResultFavicon = document . createElement ( 'img' ) ;
2023-10-16 05:47:02 +00:00
indieResultFavicon . alt = '' ;
indieResultFavicon . width = '12' ;
indieResultFavicon . height = '12' ;
2024-04-23 05:18:42 +00:00
indieResultFavicon . src = extensionAPI . runtime . getURL ( 'favicons/' + site . language . toLowerCase ( ) + '/' + site . destination _icon ) ;
2023-10-16 05:47:02 +00:00
indieResultFaviconContainer . append ( indieResultFavicon ) ;
2023-11-03 09:20:45 +00:00
let indieResultText = document . createElement ( 'span' ) ;
2024-01-07 10:01:46 +00:00
if ( originArticle && originArticle !== site [ 'origin_main_page' ] ) {
2024-03-13 08:01:41 +00:00
let destinationArticleTitle = removeSubstringIfAtEnd ( destinationArticle , site [ 'destination_content_suffix' ] ) . replace ( site [ 'destination_content_prefix' ] , '' ) . replaceAll ( '_' , ' ' ) ;
2024-01-29 07:37:33 +00:00
// If a Fextralife wiki, replace plus signs with spaces
// When there are multiple plus signs together, this regex will only replace only the first
if ( link . includes ( '.wiki.fextralife.com' ) ) {
destinationArticleTitle = destinationArticleTitle . replace ( /(?<!\+)\+/g , ' ' ) ;
}
2024-02-27 09:43:55 +00:00
2024-01-10 09:21:47 +00:00
if ( site [ 'language' ] === 'EN' && link . match ( /fandom\.com\/[a-z]{2}\/wiki\// ) ) {
2024-01-11 08:53:30 +00:00
indieResultText . innerText = 'Look up "' + decodeURIComponent ( destinationArticleTitle ) + '" on ' + site . destination + ' (EN)' ;
2023-11-06 00:59:24 +00:00
} else {
2024-01-11 08:53:30 +00:00
indieResultText . innerText = 'Look up "' + decodeURIComponent ( destinationArticleTitle ) + '" on ' + site . destination ;
2023-11-06 00:59:24 +00:00
}
2023-10-16 05:47:02 +00:00
} else {
2024-01-10 09:21:47 +00:00
if ( site [ 'language' ] === 'EN' && link . match ( /fandom\.com\/[a-z]{2}\/wiki\// ) ) {
2023-11-06 00:59:24 +00:00
indieResultText . innerText = 'Visit ' + site . destination + ' (EN) instead' ;
} else {
indieResultText . innerText = 'Visit ' + site . destination + ' instead' ;
}
2023-10-16 05:47:02 +00:00
}
indieResultButton . append ( indieResultFaviconContainer ) ;
indieResultButton . append ( indieResultText ) ;
indieResultLink . appendChild ( indieResultButton ) ;
2023-11-03 09:20:45 +00:00
// Output container for result controls:
let resultControls = document . createElement ( 'div' ) ;
resultControls . classList . add ( 'iwb-result-controls' ) ;
2023-11-02 08:35:34 +00:00
// Output link to re-enable disabled result:
2023-11-03 09:20:45 +00:00
let enableResultButton = document . createElement ( 'div' ) ;
enableResultButton . innerText = 'Re-enable the result below' ;
resultControls . prepend ( enableResultButton ) ;
2023-12-13 10:10:55 +00:00
enableResultButton . addEventListener ( 'click' , ( e ) => {
2023-11-02 08:35:34 +00:00
e . target . closest ( '.iwb-disavow' ) . classList . remove ( 'iwb-disavow' ) ;
2023-11-03 09:20:45 +00:00
e . target . classList . add ( 'iwb-hide' ) ;
2023-11-02 08:35:34 +00:00
} ) ;
2023-11-03 09:20:45 +00:00
indieContainer . appendChild ( indieResultLink ) ;
indieContainer . appendChild ( resultControls ) ;
searchResultContainer . prepend ( indieContainer ) ;
2023-11-06 05:09:14 +00:00
2023-11-06 02:25:47 +00:00
return 1 ;
2023-10-16 05:47:02 +00:00
}
2023-11-06 02:25:47 +00:00
return 0 ;
2023-10-16 05:47:02 +00:00
}
2023-12-13 09:32:24 +00:00
function hideSearchResults ( searchResultContainer , searchEngine , site , showBanner = 'on' ) {
2024-02-17 20:32:12 +00:00
// Insert search result removal notice, if enabled and not already injected
let elementId = stringToId ( site . language + '-' + site . origin ) ;
if ( showBanner === 'on' && ! document . getElementById ( 'iwb-notice-' + elementId ) ) {
2023-10-16 05:47:02 +00:00
hiddenWikisRevealed [ elementId ] = false ;
2023-11-06 05:09:14 +00:00
// Using aside to avoid conflicts with website CSS and listeners:
2023-10-16 05:47:02 +00:00
let searchRemovalNotice = document . createElement ( 'aside' ) ;
searchRemovalNotice . id = 'iwb-notice-' + elementId ;
searchRemovalNotice . classList . add ( 'iwb-notice' ) ;
let searchRemovalNoticeLink = document . createElement ( 'a' ) ;
searchRemovalNoticeLink . href = 'https://' + site . destination _base _url ;
searchRemovalNoticeLink . textContent = site . destination ;
2024-01-12 02:02:09 +00:00
searchRemovalNoticePretext = document . createTextNode ( 'Indie Wiki Buddy has filtered out results from ' + site . origin + ( site . language !== 'EN' ? ' (' + site . language + ')' : '' ) + '. Look for results from ' ) ;
2023-11-03 22:12:15 +00:00
searchRemovalNoticePosttext = document . createTextNode ( ' instead.' ) ;
2023-10-16 05:47:02 +00:00
searchRemovalNotice . appendChild ( searchRemovalNoticePretext ) ;
searchRemovalNotice . appendChild ( searchRemovalNoticeLink ) ;
searchRemovalNotice . appendChild ( searchRemovalNoticePosttext ) ;
2023-11-03 09:20:45 +00:00
// Output container for result controls:
let resultControls = document . createElement ( 'div' ) ;
resultControls . classList . add ( 'iwb-result-controls' ) ;
// Output link to show hidden results:
let showResultsButton = document . createElement ( 'div' ) ;
2023-10-16 05:47:02 +00:00
showResultsButton . setAttribute ( 'data-group' , 'iwb-search-result-' + elementId ) ;
2023-11-03 09:20:45 +00:00
showResultsButton . innerText = 'Show filtered results' ;
resultControls . appendChild ( showResultsButton )
2023-10-16 05:47:02 +00:00
showResultsButton . onclick = function ( e ) {
if ( e . target . textContent . includes ( 'Show' ) ) {
e . target . textContent = 'Re-hide filtered results' ;
hiddenWikisRevealed [ elementId ] = true ;
const selector = e . currentTarget . dataset . group ;
document . querySelectorAll ( '.' + selector ) . forEach ( el => {
el . classList . add ( 'iwb-show' ) ;
} )
} else {
e . target . textContent = 'Show filtered results' ;
hiddenWikisRevealed [ elementId ] = false ;
const selector = e . currentTarget . dataset . group ;
document . querySelectorAll ( '.' + selector ) . forEach ( el => {
el . classList . remove ( 'iwb-show' ) ;
} )
}
}
2023-11-06 05:09:14 +00:00
2023-11-03 09:20:45 +00:00
searchRemovalNotice . appendChild ( resultControls ) ;
2023-10-16 05:47:02 +00:00
switch ( searchEngine ) {
case 'google' :
if ( document . querySelector ( '#search' ) ) {
document . querySelector ( '#search' ) . prepend ( searchRemovalNotice ) ;
} else if ( document . querySelector ( '#topstuff' ) ) {
document . querySelector ( '#topstuff' ) . prepend ( searchRemovalNotice ) ;
} else if ( document . querySelector ( '#main' ) ) {
var el = document . querySelector ( '#main' ) ;
if ( el . querySelector ( '#main > div[data-hveid]' ) ) {
el . insertBefore ( searchRemovalNotice , el . querySelector ( 'div[data-hveid]' ) ) ;
} else {
el . insertBefore ( searchRemovalNotice , el . querySelector ( 'div div[data-hveid]' ) . parentElement ) ;
}
} ;
break ;
case 'bing' :
var li = document . createElement ( 'li' ) ;
li . appendChild ( searchRemovalNotice ) ;
document . querySelector ( '#b_results' ) . prepend ( li ) ;
break ;
case 'duckduckgo' :
if ( document . getElementById ( 'web_content_wrapper' ) ) {
var li = document . createElement ( 'li' ) ;
li . appendChild ( searchRemovalNotice ) ;
document . querySelector ( '#web_content_wrapper ol' ) . prepend ( li ) ;
} else {
document . getElementById ( 'links' ) . prepend ( searchRemovalNotice ) ;
}
break ;
case 'brave' :
document . querySelector ( 'body' ) . prepend ( searchRemovalNotice ) ;
break ;
case 'ecosia' :
document . querySelector ( 'body' ) . prepend ( searchRemovalNotice ) ;
break ;
2024-02-17 08:16:30 +00:00
case 'qwant' :
document . querySelector ( 'div[data-testid=sectionWeb]' ) . prepend ( searchRemovalNotice ) ;
break ;
2023-10-16 05:47:02 +00:00
case 'startpage' :
document . querySelector ( '#main' ) . prepend ( searchRemovalNotice ) ;
break ;
2024-02-18 06:41:06 +00:00
case 'yandex' :
var searchResultsContainer = document . querySelector ( '#search-result' ) || document . querySelector ( '.main__content .content' ) ;
searchResultsContainer ? . prepend ( searchRemovalNotice ) ;
break ;
2023-10-16 05:47:02 +00:00
case 'yahoo' :
if ( document . querySelector ( '#web > ol' ) ) {
var li = document . createElement ( 'li' ) ;
li . appendChild ( searchRemovalNotice ) ;
document . querySelector ( '#web > ol' ) . prepend ( li ) ;
} else {
document . querySelector ( '#main-algo' ) . prepend ( searchRemovalNotice ) ;
}
break ;
2024-02-21 23:27:37 +00:00
case 'kagi' :
document . querySelector ( '#main' ) . prepend ( searchRemovalNotice ) ;
break ;
2024-03-18 08:03:37 +00:00
case 'searxng' :
document . querySelector ( '#results' ) . prepend ( searchRemovalNotice ) ;
break ;
case 'whoogle' :
document . querySelector ( '#main' ) . prepend ( searchRemovalNotice ) ;
break ;
2023-10-16 05:47:02 +00:00
default :
}
}
if ( ! Array . from ( searchResultContainer . classList ) . includes ( 'iwb-hide' ) ) {
2024-01-12 02:02:09 +00:00
let elementId = stringToId ( site . language + '-' + site . origin ) ;
2023-10-16 05:47:02 +00:00
searchResultContainer . classList . add ( 'iwb-search-result-' + elementId ) ;
searchResultContainer . classList . add ( 'iwb-hide' ) ;
2023-11-06 02:25:47 +00:00
searchResultContainer . classList . add ( 'iwb-detected' ) ;
2023-10-16 05:47:02 +00:00
if ( hiddenWikisRevealed [ elementId ] ) {
searchResultContainer . classList . add ( 'iwb-show' ) ;
}
2023-11-06 02:25:47 +00:00
return 1 ;
2023-10-16 05:47:02 +00:00
}
2023-11-06 02:25:47 +00:00
return 0 ;
2023-10-16 05:47:02 +00:00
}
2023-08-08 06:33:23 +00:00
2024-02-04 10:19:09 +00:00
function getDistance ( child , parent ) {
let distance = 0 ;
while ( parent !== child ) {
child = child . parentNode ;
distance ++ ;
if ( ! child ) break ;
}
return distance ;
}
function findClosestElement ( target , elements ) {
let closestElement = null ;
let closestDistance = Infinity ;
elements . forEach ( element => {
const distance = element ? . contains ( target ) ? getDistance ( target , element ) : Infinity ;
if ( distance < closestDistance ) {
closestDistance = distance ;
closestElement = element ;
}
} ) ;
return closestElement ;
}
2024-04-23 05:18:42 +00:00
async function filterSearchResult ( matchingSite , searchResult , searchEngine , countFiltered , storage , reorderedHrefs ) {
2024-03-21 05:09:18 +00:00
// Get user's settings for the wiki
let id = matchingSite [ 'id' ] ;
let searchFilterSetting = 'replace' ;
let searchEngineSettings = await commonFunctionDecompressJSON ( storage . searchEngineSettings || { } ) ;
if ( searchEngineSettings [ id ] ) {
searchFilterSetting = searchEngineSettings [ id ] ;
} else if ( storage . defaultSearchAction ) {
searchFilterSetting = storage . defaultSearchAction ;
}
// Output stylesheet if not already done
if ( ! document . querySelector ( '.iwb-styles' ) ) {
const headElement = document . querySelector ( 'head' ) ;
if ( headElement ) {
insertCSS ( ) ;
} else {
// If head element doesn't exist, wait for it via MutationObserver
const docObserver = new MutationObserver ( ( mutations , mutationInstance ) => {
const headElement = document . querySelector ( 'head' ) ;
if ( headElement && ! document . querySelector ( '.iwb-styles' ) ) {
insertCSS ( ) ;
mutationInstance . disconnect ( ) ;
}
} ) ;
docObserver . observe ( document , {
childList : true ,
subtree : true
} ) ;
}
}
let searchResultContainer = null ;
switch ( searchEngine ) {
case 'google' :
const closestJsController = searchResult . closest ( 'div[jscontroller]' ) ;
const closestDataDiv = searchResult . closest ( 'div[data-hveid].g' ) || searchResult . closest ( 'div[data-hveid]' ) ;
searchResultContainer = findClosestElement ( searchResult , [ closestJsController , closestDataDiv ] ) ;
break ;
case 'bing' :
searchResultContainer = searchResult . closest ( 'li.b_algo' ) ;
break ;
case 'duckduckgo' :
searchResultContainer = searchResult . closest ( 'li[data-layout], div.web-result' ) ;
break ;
case 'brave' :
searchResultContainer = searchResult . closest ( 'div.snippet' ) ;
break ;
case 'ecosia' :
searchResultContainer = searchResult . closest ( 'div.mainline__result-wrapper article div.result__body' ) ;
break ;
case 'qwant' :
if ( searchResult . closest ( 'div[data-testid=webResult]' ) ) {
cssQuery = 'div[data-testid=webResult]' ;
searchResultContainer = searchResult . closest ( cssQuery ) . parentElement ;
}
break ;
case 'startpage' :
2024-04-23 05:18:42 +00:00
searchResultContainer = searchResult . closest ( 'div.result, div.w-gl__result' ) ;
2024-03-21 05:09:18 +00:00
break ;
case 'yandex' :
2024-04-23 05:18:42 +00:00
searchResultContainer = searchResult . closest ( 'li[data-cid], .serp-item, .MMOrganicSnippet, .viewer-snippet' ) ;
2024-03-21 05:09:18 +00:00
break ;
case 'yahoo' :
searchResultContainer = searchResult . closest ( '#web > ol > li div.itm .exp, #web > ol > li div.algo, #web > ol > li, section.algo' ) ;
break ;
case 'kagi' :
searchResultContainer = searchResult . closest ( 'div.search-result, div.__srgi' ) ;
break ;
case 'searxng' :
2024-04-23 05:18:42 +00:00
searchResultContainer = searchResult . closest ( 'article' ) ;
break ;
2024-03-21 05:09:18 +00:00
case 'whoogle' :
2024-04-23 05:18:42 +00:00
searchResultContainer = searchResult . closest ( '#main>div>div, details>div>div>div>div>div>div.has-favicon' ) ;
break ;
2024-03-21 05:09:18 +00:00
default :
}
if ( searchResultContainer ) {
// If this page from Fandom is the same as a re-ordered page, filter it out
let originArticle = commonFunctionGetOriginArticle ( searchResult . href , matchingSite ) ;
let destinationArticle = commonFunctionGetDestinationArticle ( matchingSite , originArticle ) ;
if ( reorderedHrefs . find ( ( href ) => href . match (
new RegExp (
` http(s)*:// ${ matchingSite [ 'destination_base_url' ] } ${ matchingSite [ 'destination_content_path' ] } ${ encodeURIComponent ( destinationArticle ) } `
)
) ) ) {
countFiltered += hideSearchResults ( searchResultContainer , searchEngine , matchingSite , 'off' ) ;
console . debug ( ` Indie Wiki Buddy has hidden a result matching ${ searchResult . href } because we re-ordered an indie wiki result with a matching article ` ) ;
} else if ( searchFilterSetting !== 'disabled' ) {
if ( searchFilterSetting === 'hide' ) {
// Else, if the user has the preference set to hide search results, hide it indiscriminately
countFiltered += hideSearchResults ( searchResultContainer , searchEngine , matchingSite , storage [ 'hiddenResultsBanner' ] ) ;
} else {
countFiltered += replaceSearchResults ( searchResultContainer , matchingSite , searchResultLink ) ;
}
}
}
return countFiltered ;
}
2024-04-23 05:18:42 +00:00
async function reorderDestinationSearchResult ( resultsFirstChild , searchResult ) {
2024-03-22 08:23:50 +00:00
// Find containing element for the search result
const closestJsController = searchResult . closest ( 'div[jscontroller]' ) ;
const closestDataDiv = searchResult . closest ( 'div[data-hveid].g' ) || searchResult . closest ( 'div[data-hveid]' ) ;
searchResultContainer = findClosestElement ( searchResult , [ closestJsController , closestDataDiv ] ) ;
// Find the element holding the search results,
// to prepend the destination wiki result
let searchResultsList = document . querySelector ( '#search' ) || document . querySelector ( '#topstuff' ) ;
if ( ! searchResultsList ) {
if ( document . querySelector ( '#main' ) ) {
var el = document . querySelector ( '#main' ) ;
if ( el . querySelector ( '#main > div[data-hveid]' ) ) {
searchResultsList = el . querySelector ( 'div[data-hveid]' ) ;
} else {
searchResultsList = el . querySelector ( 'div div[data-hveid]' ) . parentElement ;
}
} ;
}
2024-04-14 08:45:13 +00:00
2024-03-22 08:23:50 +00:00
if ( ! resultsFirstChild || ! searchResultContainer || searchResultContainer . classList . contains ( 'iwb-reordered' ) ) {
2024-03-21 05:09:18 +00:00
return ;
}
2024-03-22 08:23:50 +00:00
searchResultContainer . classList . add ( 'iwb-reordered' ) ;
searchResultsList . prepend ( searchResultContainer ) ;
2024-03-21 05:09:18 +00:00
}
async function reorderSearchResults ( searchResults , searchEngine , storage ) {
const reorderResultsSetting = storage . reorderResults || 'on' ;
if ( reorderResultsSetting === 'off' ) return [ ] ;
let reorderedHrefs = [ ] ;
if ( ! document . body . classList . contains ( 'iwb-reorder' ) ) {
document . body . classList . add ( 'iwb-reorder' ) ;
// Only support Google for now
if ( searchEngine !== 'google' ) return ;
// Get the first element in the results container
2024-03-22 08:23:50 +00:00
let resultsFirstChild = document . querySelector ( '#rso div[data-hveid].g' ) ||
document . querySelector ( '#main div[data-hveid].g' ) ||
2024-04-25 05:34:45 +00:00
document . querySelector ( '#rso div[data-hveid] div[data-dsrp]' ) ||
document . querySelector ( '#main div[data-hveid] div[data-dsrp]' ) ||
2024-03-22 08:23:50 +00:00
document . querySelector ( '#rso div[data-hveid]' ) ||
document . querySelector ( '#main div[data-hveid]' ) ;
2024-04-14 08:45:13 +00:00
2024-03-21 05:09:18 +00:00
if ( ! resultsFirstChild ) return ;
let crossLanguageSetting = storage . crossLanguage || 'off' ;
for ( const searchResult of searchResults ) {
try {
if ( searchResult . closest ( '.iwb-detected' ) ) {
continue ;
}
const searchResultLink = searchResult . href || '' ;
// Handle re-ordering of results to move destination results up the page
let matchingDest = await commonFunctionFindMatchingSite ( searchResultLink , crossLanguageSetting , true ) ;
if ( matchingDest ) {
2024-04-28 00:07:30 +00:00
let searchEngineSettings = await commonFunctionDecompressJSON ( storage . searchEngineSettings || { } ) ;
if ( searchEngineSettings [ matchingDest . id ] !== 'disabled' ) {
if ( resultsFirstChild . contains ( searchResult ) ) {
// If this search result is inside the first child of the results container (aka, it's the first result),
// and there is a matchingDest at this point, then an indie wiki is #1 on the search results page.
// Therefore, we should abandon the search re-ordering.
console . debug ( 'Indie Wiki Buddy is not re-ordering results, as an indie wiki is already the first result.' ) ;
break ;
} else {
await reorderDestinationSearchResult ( resultsFirstChild , searchResult ) ;
reorderedHrefs . push ( searchResultLink ) ;
}
2024-03-21 05:09:18 +00:00
}
}
} catch ( e ) {
console . log ( 'Indie Wiki Buddy failed to properly re-order search results with error: ' + e ) ;
}
}
}
2024-05-01 19:55:41 +00:00
return reorderedHrefs ;
2024-03-21 05:09:18 +00:00
}
async function filterSearchResults ( searchResults , searchEngine , storage , reorderedHrefs = [ ] ) {
2024-02-04 10:19:09 +00:00
let countFiltered = 0 ;
2024-03-21 05:09:18 +00:00
for ( const searchResult of searchResults ) {
2024-02-04 10:19:09 +00:00
try {
2024-03-21 05:09:18 +00:00
// Check that result isn't within another result
2024-04-23 05:18:42 +00:00
if ( ! searchResult . closest ( '.iwb-detected' ) || ! searchResult . closest ( '.iwb-detected' ) ? . querySelector ( '.iwb-new-link' ) ) {
2024-02-04 10:19:09 +00:00
searchResultLink = searchResult . href || '' ;
if ( ! searchResultLink ) {
continue ;
}
if ( searchEngine === 'google' ) {
// Break if image result:
if ( searchResultLink . includes ( 'imgurl=' ) ) {
break ;
2023-11-06 02:25:47 +00:00
}
2023-08-04 05:50:12 +00:00
2024-02-04 10:19:09 +00:00
// Skip if result doesn't include specific tags/attributes
// This helps avoid capturing unintended image results
if ( ! (
searchResult . querySelector ( 'h1' ) ||
searchResult . querySelector ( 'h3' ) ||
searchResult . querySelector ( 'cite' ) ||
2024-03-21 05:09:18 +00:00
searchResult . querySelector ( "div[role='link']" ) ) )
{
2024-02-04 10:19:09 +00:00
searchResult . classList . add ( 'iwb-detected' ) ;
continue ;
}
}
2023-11-06 07:14:07 +00:00
2024-02-04 10:19:09 +00:00
let crossLanguageSetting = storage . crossLanguage || 'off' ;
2023-11-06 02:25:47 +00:00
2024-03-21 05:09:18 +00:00
// Handle source -> destination filtering
let matchingSource = await commonFunctionFindMatchingSite ( searchResultLink , crossLanguageSetting ) ;
if ( matchingSource ) {
2024-04-23 05:18:42 +00:00
countFiltered = await filterSearchResult ( matchingSource , searchResult , searchEngine , countFiltered , storage , reorderedHrefs ) ;
2023-07-25 06:17:46 +00:00
}
}
2024-02-04 10:19:09 +00:00
} catch ( e ) {
console . log ( 'Indie Wiki Buddy failed to properly parse search results with error: ' + e ) ;
2023-07-25 06:17:46 +00:00
}
2024-02-04 10:19:09 +00:00
} ;
2024-02-17 07:22:08 +00:00
// Add location observer to check for additional mutations
2024-04-23 05:18:42 +00:00
addDOMChangeObserver ( startFiltering , searchEngine , storage ) ;
2024-02-17 07:22:08 +00:00
// If any results were filtered, update search filter count
2024-02-04 10:19:09 +00:00
if ( countFiltered > 0 ) {
2024-04-23 05:18:42 +00:00
extensionAPI . storage . sync . set ( { 'countSearchFilters' : ( storage . countSearchFilters ? ? 0 ) + countFiltered } ) ;
2024-02-04 10:19:09 +00:00
}
2023-07-25 06:17:46 +00:00
}
2024-04-23 05:18:42 +00:00
function startFiltering ( searchEngine , storage , mutations = null , observer = null ) {
2023-07-25 06:17:46 +00:00
if ( observer ) {
observer . disconnect ( ) ;
}
2024-04-23 05:18:42 +00:00
// Check if extension is on:
if ( ( storage . power ? ? 'on' ) === 'on' ) {
// Determine which search engine we're on
switch ( searchEngine ) {
case 'google' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Google
function filterGoogle ( reorderedHrefs ) {
let searchResults = document . querySelectorAll ( `
div [ data - hveid ] a [ href *= '.fandom.com/' ] : first - of - type : not ( [ role = 'button' ] ) : not ( [ target = '_self' ] ) ,
div [ data - hveid ] a [ href *= '.wiki.fextralife.com/' ] : first - of - type : not ( [ role = 'button' ] ) : not ( [ target = '_self' ] ) ,
div [ data - hveid ] a [ href *= '.neoseeker.com/wiki/' ] : first - of - type : not ( [ role = 'button' ] ) : not ( [ target = '_self' ] ) ` );
filterSearchResults ( searchResults , 'google' , storage , reorderedHrefs ) ;
}
2023-07-25 08:47:22 +00:00
2024-03-21 05:09:18 +00:00
async function reorderGoogle ( ) {
let searchResults = document . querySelectorAll ( "div[data-hveid] a:first-of-type:not([role='button']):not([target='_self'])" ) ;
2024-04-14 08:45:13 +00:00
// Remove any matches that are not "standard" search results - this could've been done with :has() but limited browser support right now
2024-05-01 19:50:52 +00:00
searchResults = Array . from ( searchResults ) . filter ( ( e ) => ! e . closest ( 'g-section-with-header, div[jsname]' ) ) ;
2024-04-14 08:45:13 +00:00
2024-03-21 05:09:18 +00:00
return await reorderSearchResults ( searchResults , 'google' , storage ) ;
}
2023-07-25 08:47:22 +00:00
2024-03-21 05:09:18 +00:00
reorderGoogle ( ) . then ( ( r ) => {
// Filtering happens after re-ordering, so that we can filter anything that matches what we re-ordered
filterGoogle ( r ) ;
} ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'duckduckgo' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in DuckDuckGo
function filterDuckDuckGo ( ) {
let searchResults = document . querySelectorAll ( 'h2>a[href*=".fandom.com"], h2>a[href*=".wiki.fextralife.com"], h2>a[href*=".neoseeker.com/wiki/"]' ) ;
filterSearchResults ( searchResults , 'duckduckgo' , storage ) ;
}
filterDuckDuckGo ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'bing' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Bing
function filterBing ( ) {
let searchResultsEncoded = document . querySelectorAll ( 'li.b_algo h2 a, li.b_algo .b_algoheader a' ) ;
let searchResults = [ ] ;
searchResultsEncoded . forEach ( ( searchResult ) => {
if ( searchResult . href ) {
const encodedLink = new URL ( searchResult . href ) ;
if ( encodedLink . href . includes ( 'https://www.bing.com/ck/' ) ) {
try {
let decodedLink = base64Decode ( encodedLink . searchParams . get ( 'u' ) . replace ( /^a1/ , '' ) ) ;
if ( decodedLink . includes ( '.fandom.com' ) || decodedLink . includes ( '.wiki.fextralife.com' ) || decodedLink . includes ( '.neoseeker.com/wiki/' ) ) {
searchResult . href = decodedLink ;
searchResults . push ( searchResult ) ;
2024-01-26 11:10:23 +00:00
}
2024-03-21 05:09:18 +00:00
} catch ( e ) {
console . log ( 'Indie Wiki Buddy failed to parse Bing link with error: ' , e ) ;
2024-01-26 11:10:23 +00:00
}
2024-03-21 05:09:18 +00:00
} else {
searchResults . push ( searchResult ) ;
2024-01-26 11:10:23 +00:00
}
2024-03-21 05:09:18 +00:00
}
} ) ;
2024-01-26 11:10:23 +00:00
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'bing' , storage ) ;
}
2023-07-25 08:47:22 +00:00
2024-03-21 05:09:18 +00:00
filterBing ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'brave' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Brave
function filterBrave ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'div.snippet[data-type="web"] a' ) ) . filter ( el =>
2024-02-17 07:22:08 +00:00
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'brave' , storage ) ;
}
2023-07-25 08:47:22 +00:00
2024-03-21 05:09:18 +00:00
filterBrave ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'ecosia' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Ecosia
function filterEcosia ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'section.mainline .result__title a.result__link' ) ) . filter ( el =>
2024-02-17 07:22:08 +00:00
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'ecosia' , storage ) ;
}
2023-07-25 08:47:22 +00:00
2024-03-21 05:09:18 +00:00
filterEcosia ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'qwant' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Qwant
function filterQwant ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'a[data-testid=serTitle]' ) ) . filter ( el => el . href . includes ( 'fandom.com' ) || el . href . includes ( 'fextralife.com' ) ) ;
filterSearchResults ( searchResults , 'qwant' , storage ) ;
}
2024-02-17 08:16:30 +00:00
2024-03-21 05:09:18 +00:00
filterQwant ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'startpage' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Startpage
function filterStartpage ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'a.result-link' ) ) . filter ( el =>
2024-02-17 07:22:08 +00:00
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'startpage' , storage ) ;
}
2023-07-25 08:47:22 +00:00
2024-03-21 05:09:18 +00:00
filterStartpage ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'yandex' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Yandex
function filterYandex ( ) {
2024-04-23 05:18:42 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'li[data-cid] a.link, li[data-cid] a.Link, .serp-item a.link, .serp-item a.Link, .MMOrganicSnippet a, .viewer-snippet a' ) ) . filter ( el =>
2024-02-18 06:41:06 +00:00
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'yandex' , storage ) ;
}
2024-02-18 06:41:06 +00:00
2024-03-21 05:09:18 +00:00
filterYandex ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'yahoo' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Yahoo
function filterYahoo ( ) {
let searchResultsEncoded = document . querySelectorAll ( '#web > ol > li a:not(.thmb), #main-algo section.algo a:not(.thmb)' ) ;
let searchResults = [ ] ;
searchResultsEncoded . forEach ( ( searchResult ) => {
if ( searchResult . href ) {
if ( searchResult . href . includes ( 'https://r.search.yahoo.com/' ) ) {
try {
// Extract the URL between "RU=" and "/RK="
const embeddedUrlRegex = /RU=([^/]+)\/RK=/ ;
const match = searchResult . href . match ( embeddedUrlRegex ) ;
const extractedURL = decodeURIComponent ( match && match [ 1 ] ) ;
if ( extractedURL . includes ( '.fandom.com' ) || extractedURL . includes ( '.wiki.fextralife.com' ) || extractedURL . includes ( '.neoseeker.com/wiki/' ) ) {
searchResult . href = extractedURL ;
searchResults . push ( searchResult ) ;
2024-02-18 07:57:59 +00:00
}
2024-03-21 05:09:18 +00:00
} catch ( e ) {
console . log ( 'Indie Wiki Buddy failed to parse Yahoo link with error: ' , e ) ;
2024-02-18 07:57:59 +00:00
}
2024-03-21 05:09:18 +00:00
} else {
searchResults . push ( searchResult ) ;
2024-02-18 07:57:59 +00:00
}
2024-03-21 05:09:18 +00:00
}
} ) ;
2024-02-18 07:57:59 +00:00
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'yahoo' , storage ) ;
}
2023-08-08 06:33:23 +00:00
2024-03-21 05:09:18 +00:00
filterYahoo ( ) ;
2024-04-23 05:18:42 +00:00
break ;
case 'kagi' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Kagi
function filterKagi ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'h3>a, a.__sri-url' ) ) . filter ( el =>
2024-02-21 23:42:10 +00:00
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'kagi' , storage ) ;
}
2024-02-21 23:27:37 +00:00
2024-03-21 05:09:18 +00:00
filterKagi ( ) ;
2024-04-23 05:18:42 +00:00
break ;
default :
if ( storage . customSearchEngines ) {
function filterSearXNG ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'h3>a' ) ) . filter ( el =>
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
filterSearchResults ( searchResults , 'searxng' , storage ) ;
}
2024-03-18 08:03:37 +00:00
2024-04-23 05:18:42 +00:00
function filterWhoogle ( ) {
let searchResults = Array . from ( document . querySelectorAll ( 'div>a' ) ) . filter ( el =>
el . href ? . includes ( '.fandom.com' ) ||
el . href ? . includes ( '.wiki.fextralife.com' ) ||
el . href ? . includes ( '.neoseeker.com/wiki/' ) ) ;
filterSearchResults ( searchResults , 'whoogle' , storage ) ;
}
2024-03-18 08:03:37 +00:00
2024-04-23 05:18:42 +00:00
function filter ( searchEngine ) {
if ( searchEngine === 'searxng' ) {
filterSearXNG ( ) ;
} else if ( searchEngine === 'whoogle' ) {
filterWhoogle ( ) ;
}
2024-03-18 08:03:37 +00:00
}
2024-04-23 05:18:42 +00:00
let customSearchEngines = storage . customSearchEngines ;
if ( customSearchEngines [ currentURL . hostname ] ) {
let customSearchEnginePreset = customSearchEngines [ currentURL . hostname ] ;
filter ( customSearchEnginePreset ) ;
}
2023-07-25 06:17:46 +00:00
}
2024-04-23 05:18:42 +00:00
}
}
}
// Check if user has enabled filtering for the current search engine
// If so, call startFiltering function to start filtering process
function checkIfEnabled ( searchEngine ) {
extensionAPI . runtime . sendMessage ( { action : 'getStorage' } , ( storage ) => {
searchEngineToggles = storage . searchEngineToggles || { } ;
if ( searchEngineToggles [ searchEngine ] === 'on' || ! searchEngineToggles . hasOwnProperty ( searchEngine ) ) {
startFiltering ( searchEngine , storage ) ;
2024-03-21 05:09:18 +00:00
}
2023-07-25 06:17:46 +00:00
} ) ;
}
2024-04-23 05:18:42 +00:00
// Figure out which search engine we're on
if ( currentURL . hostname . includes ( 'www.google.' ) ) {
checkIfEnabled ( 'google' ) ;
} else if ( currentURL . hostname . includes ( 'duckduckgo.com' ) && ( currentURL . search . includes ( 'q=' ) || currentURL . pathname . includes ( 'html' ) ) ) {
checkIfEnabled ( 'duckduckgo' ) ;
} else if ( currentURL . hostname . endsWith ( '.bing.com' ) ) {
checkIfEnabled ( 'bing' ) ;
} else if ( currentURL . hostname . includes ( 'search.brave.com' ) ) {
checkIfEnabled ( 'brave' ) ;
} else if ( currentURL . hostname . includes ( 'ecosia.org' ) ) {
checkIfEnabled ( 'ecosia' ) ;
} else if ( currentURL . hostname . includes ( 'qwant.com' ) ) {
checkIfEnabled ( 'qwant' ) ;
} else if ( currentURL . hostname . includes ( 'startpage.com' ) ) {
checkIfEnabled ( 'startpage' ) ;
} else if ( currentURL . hostname . includes ( 'yandex.' ) || currentURL . hostname . includes ( 'ya.ru' ) ) {
checkIfEnabled ( 'yandex' ) ;
} else if ( currentURL . hostname . includes ( 'yahoo.com' ) ) {
checkIfEnabled ( 'yahoo' ) ;
} else if ( currentURL . hostname . includes ( 'kagi.com' ) ) {
checkIfEnabled ( 'kagi' ) ;
}