NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Shopee Advanced Search // @description Filter search results containing ALL specified words, supporting word exclusion and minimum sold. // @version 1.3.0 // @author icetbr // @icon https://www.google.com/s2/favicons?sz=64&domain=shopee.com.br // @include https://shopee.*/* // @license MIT // @namespace https://github.com/icetbr/userscripts // @updateURL https://openuserjs.org/meta/icetbr/Shopee_Advanced_Search.meta.js // @downloadURL https://openuserjs.org/src/scripts/icetbr/Shopee_Advanced_Search.user.js // @match <all_urls> // @grant none // ==/UserScript== const $ = (selector, parent = document) => parent.querySelector(selector), $$ = (selector, parent = document) => Array.from(parent.querySelectorAll(selector)), el = (name, attrs) => { var $e = document.createElement(name); for (let prop in attrs) { $e.setAttribute(prop, attrs[prop]); } return $e; }, toBase64 = svg => `data:image/svg+xml;base64,${window.btoa(svg)}`, toSearchable = string => string .trim() .toLowerCase() .normalize('NFD') .replace(/\p{Diacritic}/gu, ''), isBrazil = () => window.location.hostname.endsWith('.br'), onMutation = (fn, parent = document.body) => new MutationObserver(fn) .observe(parent, { childList: true, subtree: true }); const split = value => value ? value.split(' ') : []; const filterIconSvg = ` <svg width="26px" height="26px" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg"> <g stroke="currentColor"> <path d="m4.5 7.5h12"/> <path d="m6.5 10.5h8"/> <path d="m8.5 13.5h4"/> </g> </svg>`; const isThousands = string => ['k', 'mil'].some(s => string?.includes(s)); const parseNumber = string => { let number = parseFloat(string?.replace(',', '.').match(/[\d\.]+/g)); number = isThousands(string) ? number * 1000 : number; return isNaN(number) ? 0 : number; }; const filter = ($searchedWordsInput, $excludedWordsInput, $minimumSoldInput) => () => { const $products = $$('.shopee-search-item-result__item'); const searchedWords = split(toSearchable($searchedWordsInput.value)); const excludedWords = split(toSearchable($excludedWordsInput.value)); const minimumSold = parseNumber($minimumSoldInput.value); const lacksAllSearchedWords = element => !searchedWords.every(w => element.dataset.searchableText.includes(w)); const hasAnyExcludedWords = element => excludedWords.some(w => element.dataset.searchableText.includes(w)); const hasSoldLessThan = element => element.dataset.soldCount < minimumSold; const withSearchableText = el => { const contentEl = el?.firstChild?.firstChild?.firstChild?.firstChild?.children[1]; const nameEl = contentEl?.children[0]; el.dataset.searchableText = toSearchable(nameEl?.textContent ?? ''); return el; }; const withSoldCount = el => { const contentEl = el?.firstChild?.firstChild?.firstChild?.firstChild?.children[1]; const ratingEl = contentEl?.children[1]?.children[1]; el.dataset.soldCount = parseNumber(ratingEl?.textContent ?? 0); return el; }; const toggleHidden = (counts, el) => { if (lacksAllSearchedWords(el) || hasAnyExcludedWords(el)) { el.style.display = 'none'; counts[0]++; } else if (!isNaN(minimumSold) && hasSoldLessThan(el)){ el.style.display = 'none'; counts[1]++; } else { el.style.display = 'block'; } return counts; }; let $loadedProducts = $products .map(withSearchableText) .filter(p => p.dataset.searchableText); if (!isNaN(minimumSold)) { $loadedProducts.map(withSoldCount); } const hiddenCounts = $loadedProducts.reduce(toggleHidden, [0, 0]); const excludedMsg = excludedWords.length ? ` -'${excludedWords.join(' ')}'` : ''; console.log( $products.length + ' products, ' + $loadedProducts.length + ' loaded, ' + `${hiddenCounts[0]} hidden for '${searchedWords.join(' ')}'${excludedMsg},` + `${hiddenCounts[1]} hidden for less than ${minimumSold} sold` ); }; let filterProducts; const init = () => { filterProducts && filterProducts(); const $searchBar = $('.shopee-searchbar-input'); if (!$searchBar || $searchBar.querySelector('#excludedWords')) return; console.log('shopee filter enabled'); const $searchedWordsInput = $('.shopee-searchbar-input__input'); const $minimumSoldInput = el('input', { id: 'minimumSold', style: 'width: 70px;', placeholder: isBrazil() ? 'vendido X+' : 'sold X+', onkeyup: function(e) { if (e.key === 'Enter') filterProducts(); } }); const $excludedWordsInput = el('input', { id: 'excludedWords', placeholder: isBrazil() ? 'excluir palavras' : 'exclude words', onkeyup: function(e) { if (e.key === 'Enter') filterProducts(); } }); filterProducts = filter($searchedWordsInput, $excludedWordsInput, $minimumSoldInput); const $filterButton = el('button', { type: 'button', onclick: filterProducts, style: ` background: no-repeat url(${toBase64(filterIconSvg)}); padding: 13px; margin-top: 3px; border: none; `, }); $searchBar.appendChild($minimumSoldInput); $searchBar.appendChild($excludedWordsInput); $searchBar.appendChild($filterButton); }; onMutation(init);