Amazon Filter: Sold by Amazon.com

Enhances Amazon's sidebar by adding direct filters for items sold by Amazon.com and other Amazon-owned sellers.

// ==UserScript==
// @name         Amazon Filter: Sold by Amazon.com
// @namespace    https://github.com/sinazadeh/userscripts
// @version      2.2.0
// @description  Enhances Amazon's sidebar by adding direct filters for items sold by Amazon.com and other Amazon-owned sellers.
// @author       TheSina
// @match        *://www.amazon.com/s*
// @match        *://www.amazon.com/*/b/*
// @match        *://www.amazon.com/b/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==
/* jshint esversion: 11 */
(function () {
    'use strict';

    // --- Configuration ---
    const ID_MAP = {
        Amazon: 'A2Q1LRYTXHYQ2K',
        'Amazon.com': 'ATVPDKIKX0DER',
        'Amazon Resale': 'A2L77EE7U53NWQ',
        'Amazon.com Services LLC': 'A3ODHND3J0WMC8',
    };

    const REVERSE_ID_MAP = Object.fromEntries(
        Object.entries(ID_MAP).map(([key, value]) => [value, key]),
    );
    const OPTIONS = Object.keys(ID_MAP);

    // --- State Management ---
    const state = {
        selectedFilters: [],
        currentUrl: '',
        observer: null,
        uiInitialized: false,
    };

    // --- Core Logic ---

    function applyFilters() {
        const baseUrl = isOnCategoryPage()
            ? buildSearchUrlFromCategory()
            : location.href;
        const url = new URL(baseUrl);
        const params = url.searchParams;

        const otherRhParams = (params.get('rh') || '')
            .split(',')
            .filter(p => p && !p.startsWith('p_6:'));

        if (state.selectedFilters.length > 0) {
            const sellerIds = state.selectedFilters
                .map(label => ID_MAP[label])
                .join('%7C');
            otherRhParams.push(`p_6:${sellerIds}`);
            params.set('rh', otherRhParams.join(','));
        } else {
            if (otherRhParams.length > 0) {
                params.set('rh', otherRhParams.join(','));
            } else {
                params.delete('rh');
            }
        }

        if (url.href !== location.href) {
            window.location.href = url.href;
        }
    }

    function initializeStateFromURL() {
        const url = new URL(location.href);
        const rh = url.searchParams.get('rh') || '';
        const sellerFilter = rh.split(',').find(p => p.startsWith('p_6:'));

        if (sellerFilter) {
            const sellerIds = sellerFilter.substring(4).split(/\||%7C/);
            state.selectedFilters = sellerIds
                .map(id => REVERSE_ID_MAP[id])
                .filter(Boolean);
        } else {
            state.selectedFilters = [];
        }
        state.currentUrl = location.href;
        state.uiInitialized = false;
    }

    // --- UI Creation ---

    function createUI() {
        if (state.uiInitialized) return;

        const sidebar = findSidebar();
        if (sidebar) {
            const filterContainer =
                sidebar.querySelector('#filter-p_6') || sidebar;
            buildNativeUI(filterContainer);
        } else if (isOnCategoryPage()) {
            createFloatingWidget();
        }
    }

    function buildNativeUI(container) {
        OPTIONS.forEach(label =>
            document.getElementById(`p_6/${ID_MAP[label]}`)?.remove(),
        );

        const fragment = document.createDocumentFragment();
        OPTIONS.slice().forEach(label => {
            fragment.appendChild(createCheckboxListItem(label));
        });
        container.prepend(fragment);

        const sellerBlock = container.closest('div[id*="p_6"]');
        sellerBlock?.parentElement?.prepend(sellerBlock);

        updateCheckboxes();
        state.uiInitialized = true;
    }

    function createFloatingWidget() {
        document.getElementById('amazon-seller-filter-widget')?.remove();

        const widget = document.createElement('div');
        widget.id = 'amazon-seller-filter-widget';
        Object.assign(widget.style, {
            position: 'fixed',
            top: '80px',
            right: '20px',
            background: 'white',
            border: '1px solid #ddd',
            borderRadius: '4px',
            padding: '15px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
            zIndex: '1001',
            fontFamily: 'Amazon Ember, Arial, sans-serif',
            fontSize: '14px',
        });

        widget.innerHTML = `<h3 style="margin: 0 0 10px; font-size: 16px; font-weight: bold;">Filter by Seller</h3>`;
        const ul = document.createElement('ul');
        ul.style.cssText = 'list-style: none; padding: 0; margin: 0;';

        OPTIONS.forEach(label =>
            ul.appendChild(createCheckboxListItem(label, true)),
        );

        widget.appendChild(ul);
        document.body.appendChild(widget);
        updateCheckboxes();
        state.uiInitialized = true;
    }

    function createCheckboxListItem(label, isWidget = false) {
        const li = document.createElement('li');
        li.id = `p_6/${ID_MAP[label]}`;

        if (isWidget) {
            li.innerHTML = `
                <label style="display: flex; align-items: center; cursor: pointer; margin: 8px 0;">
                    <input type="checkbox" data-label="${label}" style="margin-right: 8px;">
                    <span>${label}</span>
                </label>`;
        } else {
            li.className = 'a-list-item a-spacing-micro';
            li.innerHTML = `
                <a class="a-link-normal s-navigation-item" href="#">
                    <div class="a-checkbox a-checkbox-fancy s-navigation-checkbox aok-float-left">
                        <label><input type="checkbox" data-label="${label}"><i class="a-icon a-icon-checkbox"></i></label>
                    </div>
                    <span class="a-size-base a-color-base">${label}</span>
                </a>`;
        }

        const checkbox = li.querySelector('input[type=checkbox]');
        const clickableArea =
            li.querySelector('a') || li.querySelector('label');

        const handleChange = e => {
            e.preventDefault();
            if (e.target !== checkbox) {
                checkbox.checked = !checkbox.checked;
            }

            const idx = state.selectedFilters.indexOf(label);

            if (checkbox.checked && idx === -1) {
                state.selectedFilters.push(label);
            } else if (!checkbox.checked && idx !== -1) {
                state.selectedFilters.splice(idx, 1);
            }
            applyFilters();
        };

        clickableArea.addEventListener('click', handleChange);
        return li;
    }

    function updateCheckboxes() {
        OPTIONS.forEach(label => {
            const checkbox = document.querySelector(
                `input[data-label="${label}"]`,
            );
            if (checkbox) {
                checkbox.checked = state.selectedFilters.includes(label);
            }
        });
    }

    // --- Page Data & Helpers ---

    /**
     * Correctly identifies category pages, including path /b and /b/
     */
    const isOnCategoryPage = () =>
        location.pathname === '/b' || location.pathname.startsWith('/b/');

    function findSidebar() {
        const selectors = [
            '#s-refinements',
            '#leftNav',
            '#departments',
            'div[data-csa-c-slot-id="left-nav"]',
        ];
        return (
            selectors.map(s => document.querySelector(s)).find(el => el) || null
        );
    }

    /**
     * Builds a search URL from a category page, now with better data extraction.
     */
    function buildSearchUrlFromCategory() {
        const {node, categoryName, searchAlias} = getCategoryPageData();
        const params = new URLSearchParams();

        // Prefer using the more specific search alias 'i' if found
        if (searchAlias) {
            params.set('i', searchAlias);
        } else {
            // Fallback to keyword 'k' if no alias is available
            params.set('k', categoryName);
        }

        // Always include the node in the 'rh' parameter if it exists
        if (node) {
            params.set('rh', `n:${node}`);
        }

        return `https://www.amazon.com/s?${params.toString()}`;
    }

    /**
     * Extracts page data, now including the search alias (e.g., 'i=tools').
     */
    function getCategoryPageData() {
        const node = new URL(location.href).searchParams.get('node');
        let categoryName = '';
        let searchAlias = '';

        // Attempt to get the specific search-alias from the department dropdown
        const searchDropdown = document.getElementById('searchDropdownBox');
        if (searchDropdown) {
            const selectedOption =
                searchDropdown.querySelector('option[selected]');
            if (selectedOption) {
                const aliasValue = selectedOption.value; // e.g., "search-alias=tools"
                if (aliasValue.startsWith('search-alias=')) {
                    searchAlias = aliasValue.split('=')[1];
                }
            }
        }

        // Fallback logic to get a display name for the category
        const selectors = [
            '.sg-col .a-color-state.a-text-bold',
            '#wayfinding-breadcrumbs_feature_div .a-link-normal:last-of-type',
            'h1, .a-size-large.a-spacing-none.a-color-base',
        ];
        categoryName =
            selectors
                .map(s =>
                    document
                        .querySelector(s)
                        ?.textContent.trim()
                        .replace(/"/g, ''),
                )
                .find(name => name) || '';

        if (!categoryName) {
            const title = document.title;
            if (title.includes('Amazon.com')) {
                categoryName = title
                    .split(' - ')[0]
                    .replace('Amazon.com: ', '')
                    .trim();
            }
        }

        return {node, categoryName: categoryName || 'Category', searchAlias};
    }

    // --- Initialization and Observation ---

    function handleCategoryPageRedirect() {
        // Only redirect on category pages that LACK a standard filter sidebar.
        if (isOnCategoryPage() && !findSidebar()) {
            const searchUrl = buildSearchUrlFromCategory();
            // Use replace to avoid polluting browser history with the redirect.
            window.location.replace(searchUrl);
            return true; // Indicates a redirect has started.
        }
        return false;
    }

    function reinitialize() {
        // If the page is a basic category page, redirect to a search page first.
        if (handleCategoryPageRedirect()) {
            return; // Stop execution to allow the redirect to happen.
        }

        document.getElementById('amazon-seller-filter-widget')?.remove();
        initializeStateFromURL();
        createUI();
    }

    function setupMutationObserver() {
        if (state.observer) state.observer.disconnect();
        const observerTarget =
            document.getElementById('a-page') || document.body;

        state.observer = new MutationObserver(mutations => {
            if (location.href !== state.currentUrl) {
                reinitialize();
                return;
            }
            if (!state.uiInitialized) {
                for (const mutation of mutations) {
                    if (mutation.addedNodes.length > 0) {
                        createUI();
                        if (state.uiInitialized) break;
                    }
                }
            }
        });

        state.observer.observe(observerTarget, {
            childList: true,
            subtree: true,
        });
    }

    // --- Script Entry Point ---

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            reinitialize();
            setupMutationObserver();
        });
    } else {
        reinitialize();
        setupMutationObserver();
    }

    window.addEventListener('popstate', () => setTimeout(reinitialize, 100));
})();