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 ) ;
} ;
2024-05-02 21:56:12 +00:00
2024-04-23 05:18:42 +00:00
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 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
}
2024-03-13 08:01:41 +00:00
function removeSubstringIfAtEnd ( str , sub ) {
if ( sub && str . endsWith ( sub ) ) {
2024-05-29 20:24:58 +00:00
return str . slice ( 0 , - sub . length ) ;
2024-03-13 08:01:41 +00:00
}
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-06-07 04:43:54 +00:00
if ( originArticle && decodeURIComponent ( 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-06-04 04:03:07 +00:00
// Decode article
destinationArticleTitle = decodeURIComponent ( destinationArticleTitle ) ;
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-06-04 04:03:07 +00:00
indieResultText . innerText = 'Look up "' + destinationArticleTitle + '" on ' + site . destination + ' (EN)' ;
2023-11-06 00:59:24 +00:00
} else {
2024-06-04 04:03:07 +00:00
indieResultText . innerText = 'Look up "' + 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-05-29 20:24:58 +00:00
function getSearchContainer ( searchEngine , searchResult ) {
2024-03-21 05:09:18 +00:00
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 :
}
2024-05-29 20:24:58 +00:00
return searchResultContainer ;
}
async function filterSearchResult ( matchingSite , searchResult , searchEngine , countFiltered , storage , reorderedHrefs ) {
// 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 ;
}
const searchResultContainer = getSearchContainer ( searchEngine , searchResult ) ;
2024-03-21 05:09:18 +00:00
if ( searchResultContainer ) {
// If this page from Fandom is the same as a re-ordered page, filter it out
2024-06-04 04:03:07 +00:00
let searchResultLink = searchResult . getAttribute ( 'data-iwb-href' ) || searchResult . href ;
2024-05-29 20:24:58 +00:00
let originArticle = commonFunctionGetOriginArticle ( searchResultLink , matchingSite ) ;
2024-03-21 05:09:18 +00:00
let destinationArticle = commonFunctionGetDestinationArticle ( matchingSite , originArticle ) ;
2024-06-04 04:03:07 +00:00
2024-03-21 05:09:18 +00:00
if ( reorderedHrefs . find ( ( href ) => href . match (
2024-06-04 04:03:07 +00:00
// Match for destination URL with content path and article name
2024-03-21 05:09:18 +00:00
new RegExp (
2024-06-04 04:03:07 +00:00
` http(s)?:// ${ matchingSite [ 'destination_base_url' ] } ${ matchingSite [ 'destination_content_path' ] } ${ decodeURI ( destinationArticle ) } $ `
2024-03-21 05:09:18 +00:00
)
) ) ) {
countFiltered += hideSearchResults ( searchResultContainer , searchEngine , matchingSite , 'off' ) ;
2024-06-04 04:03:07 +00:00
console . debug ( ` Indie Wiki Buddy has hidden a result matching ${ searchResultLink } because we re-ordered an indie wiki result with a matching article ` ) ;
2024-03-21 05:09:18 +00:00
} 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-05-29 20:24:58 +00:00
async function reorderDestinationSearchResult ( firstNonIndieResult , searchResult ) {
// Find containing element for non-indie result
const nonIndieSearchResultContainer = getSearchContainer ( 'google' , firstNonIndieResult ) ;
// Find containing element for the indie search result
const indieSearchResultContainer = getSearchContainer ( 'google' , searchResult ) ;
2024-04-14 08:45:13 +00:00
2024-05-29 20:24:58 +00:00
if ( ! indieSearchResultContainer || indieSearchResultContainer . classList . contains ( 'iwb-reordered' ) ) {
2024-03-21 05:09:18 +00:00
return ;
}
2024-05-29 20:24:58 +00:00
indieSearchResultContainer . classList . add ( 'iwb-reordered' ) ;
// Prepend search results to first Fandom/Fextra/Neoseeker result
nonIndieSearchResultContainer . parentNode . prepend ( indieSearchResultContainer ) ;
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-05-29 20:24:58 +00:00
const resultsFirstChild = document . querySelector ( '#rso div[data-hveid].g' ) ||
document . querySelector ( '#main div[data-hveid].g' ) ||
document . querySelector ( '#rso div[data-hveid] div[data-dsrp]' ) ||
document . querySelector ( '#main div[data-hveid] div[data-dsrp]' ) ||
document . querySelector ( '#rso div[data-hveid]' ) ||
document . querySelector ( '#main div[data-hveid]' ) ;
// Get the first Fandom/Fextralife/Neoseeker result, if it exists
2024-06-08 18:52:50 +00:00
const nonIndieResults = Array . from ( document . querySelectorAll ( ` div[data-hveid] a:first-of-type:not([role='button']):not([target='_self']) ` ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
2024-06-04 04:03:07 +00:00
const firstNonIndieResult = Array . from ( nonIndieResults ) . filter ( ( e ) => ! e . closest ( 'g-section-with-header, div[aria-expanded], div[data-q], div[data-minw], div[data-num-cols], div[data-docid], div[data-lpage]' ) ) [ 0 ] ;
2024-05-29 20:24:58 +00:00
if ( ! resultsFirstChild || ! firstNonIndieResult ) return ;
searchResults . some ( ( result , i ) => {
2024-06-08 18:52:50 +00:00
if ( isNonIndieSite ( result . href ) ) {
2024-05-29 20:24:58 +00:00
searchResults . splice ( 0 , i + 1 ) ;
return true ;
}
return false ;
} ) ;
2024-03-21 05:09:18 +00:00
let crossLanguageSetting = storage . crossLanguage || 'off' ;
2024-05-01 23:59:28 +00:00
let resultsToSort = [ ] ;
2024-03-21 05:09:18 +00:00
for ( const searchResult of searchResults ) {
try {
if ( searchResult . closest ( '.iwb-detected' ) ) {
continue ;
}
2024-06-04 04:03:07 +00:00
const searchResultLink = searchResult . getAttribute ( 'data-iwb-href' ) || searchResult . href || '' ;
2024-03-21 05:09:18 +00:00
// Handle re-ordering of results to move destination results up the page
let matchingDest = await commonFunctionFindMatchingSite ( searchResultLink , crossLanguageSetting , true ) ;
if ( matchingDest ) {
2024-05-02 21:56:12 +00:00
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 ;
}
2024-04-28 00:07:30 +00:00
let searchEngineSettings = await commonFunctionDecompressJSON ( storage . searchEngineSettings || { } ) ;
if ( searchEngineSettings [ matchingDest . id ] !== 'disabled' ) {
2024-05-02 21:56:12 +00:00
resultsToSort . push ( searchResult ) ;
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 23:59:28 +00:00
// Reverse order of resultsToSort,
// to restore top-down order.
resultsToSort = resultsToSort . reverse ( ) ;
for ( const searchResult of resultsToSort ) {
try {
2024-05-29 20:24:58 +00:00
await reorderDestinationSearchResult ( firstNonIndieResult , searchResult ) ;
2024-06-04 04:03:07 +00:00
reorderedHrefs . push ( searchResult . getAttribute ( 'data-iwb-href' ) || searchResult . href ) ;
2024-05-01 23:59:28 +00:00
} catch ( e ) {
console . log ( 'Indie Wiki Buddy failed to properly re-order search results with error: ' + e ) ;
}
}
2024-03-21 05:09:18 +00:00
}
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-06-04 04:03:07 +00:00
let searchResultLink = searchResult . getAttribute ( 'data-iwb-href' ) || searchResult . href || '' ;
2024-02-04 10:19:09 +00:00
if ( ! searchResultLink ) {
continue ;
}
if ( searchEngine === 'google' ) {
// Break if image result:
if ( searchResultLink . includes ( 'imgurl=' ) ) {
2024-05-29 20:24:58 +00:00
continue ;
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-05-29 20:24:58 +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-06-08 18:52:50 +00:00
// Detects whether a given link is to a supported non-indie wikifarm site
function isNonIndieSite ( link ) {
return link ? . includes ( '.fandom.com' ) || link ? . includes ( '.wiki.fextralife.com' ) || link ? . includes ( '.neoseeker.com/wiki/' ) ;
}
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-06-04 04:03:07 +00:00
// Query Google results and rewrite HREFs when Google uses middleman links (i.e. google.com/url?q=)
let searchResults = document . querySelectorAll ( "div[data-hveid] a:first-of-type:not([role='button']):not([target='_self'])" ) ;
searchResults . forEach ( ( searchResult ) => {
if ( searchResult . href ) {
const link = new URL ( searchResult . href ) ;
if ( link . href . includes ( 'https://www.google.com/url' ) ) {
try {
const destinationLink = link . searchParams . get ( 'url' ) || link . searchParams . get ( 'q' ) ;
searchResult . setAttribute ( 'data-iwb-href' , destinationLink ) ;
} catch ( e ) {
2024-06-08 18:52:50 +00:00
console . log ( 'Indie Wiki Buddy failed to parse Google link with error: ' , e ) ;
2024-06-04 04:03:07 +00:00
}
}
}
} ) ;
2024-03-21 05:09:18 +00:00
// Function to filter search results in Google
function filterGoogle ( reorderedHrefs ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( ` div[data-hveid] a:first-of-type:not([role='button']):not([target='_self']) ` ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
2024-05-29 20:24:58 +00:00
2024-03-21 05:09:18 +00:00
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-03 13:20:52 +00:00
searchResults = Array . from ( searchResults ) . filter ( ( e ) => ! e . closest ( 'g-section-with-header, div[aria-expanded], div[data-q], div[data-minw], div[data-num-cols], div[data-docid], div[data-lpage]' ) ) ;
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'duckduckgo' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in DuckDuckGo
function filterDuckDuckGo ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'h2>a' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
2024-03-21 05:09:18 +00:00
filterSearchResults ( searchResults , 'duckduckgo' , storage ) ;
}
filterDuckDuckGo ( ) ;
2024-04-23 05:18:42 +00:00
break ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
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/ , '' ) ) ;
2024-06-08 18:52:50 +00:00
if ( isNonIndieSite ( decodedLink ) ) {
2024-06-04 04:03:07 +00:00
searchResult . setAttribute ( 'data-iwb-href' , decodedLink ) ;
2024-03-21 05:09:18 +00:00
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'brave' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Brave
function filterBrave ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'div.snippet[data-type="web"] a' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'ecosia' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Ecosia
function filterEcosia ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'section.mainline .result__title a.result__link' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'qwant' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Qwant
function filterQwant ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'a.external' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
2024-03-21 05:09:18 +00:00
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'startpage' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Startpage
function filterStartpage ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'a.result-link' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'yandex' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Yandex
function filterYandex ( ) {
2024-06-08 18:52:50 +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 => isNonIndieSite ( el . href ) ) ;
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
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 ] ) ;
2024-06-08 18:52:50 +00:00
if ( isNonIndieSite ( extractedURL ) ) {
2024-06-04 04:03:07 +00:00
searchResult . setAttribute ( 'data-iwb-href' , extractedURL ) ;
2024-03-21 05:09:18 +00:00
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
case 'kagi' :
2024-03-21 05:09:18 +00:00
// Function to filter search results in Kagi
function filterKagi ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'h3>a, a.__sri-url' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
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 ;
2024-06-08 18:52:50 +00:00
2024-04-23 05:18:42 +00:00
default :
if ( storage . customSearchEngines ) {
function filterSearXNG ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'h3>a' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
2024-04-23 05:18:42 +00:00
filterSearchResults ( searchResults , 'searxng' , storage ) ;
}
2024-03-18 08:03:37 +00:00
2024-04-23 05:18:42 +00:00
function filterWhoogle ( ) {
2024-06-08 18:52:50 +00:00
let searchResults = Array . from ( document . querySelectorAll ( 'div>a' ) ) . filter ( el => isNonIndieSite ( el . href ) ) ;
2024-04-23 05:18:42 +00:00
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 ) {
2024-05-29 20:24:58 +00:00
extensionAPI . runtime . sendMessage ( { action : 'getStorage' } , ( storage ) => {
2024-04-23 05:18:42 +00:00
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' ) ;
2024-05-02 21:56:12 +00:00
}