// ==UserScript== // @name Amazon公式セラー絞り込みトグル 🔄 // @name:ja Amazon公式セラー絞り込みトグル 🔄 // @name:en Amazon Official Seller Filter Toggle 🔄 // @name:zh-CN Amazon官方卖家筛选切换器 🔄 // @name:zh-TW Amazon官方賣家篩選切換器 🔄 // @name:ko 아마존 공식 셀러 필터 토글 🔄 // @name:fr Filtre Amazon Vendeur Officiel 🔄 // @name:es Filtro de Vendedor Oficial de Amazon 🔄 // @name:de Amazon Offizieller Verkäufer-Filter 🔄 // @name:pt-BR Alternador de Filtro do Vendedor Oficial da Amazon 🔄 // @name:ru Переключатель фильтра официального продавца Amazon 🔄 // @version 14.3 // @description Amazon検索結果に「Amazon公式セラー(p_6:AN1VRQENFRJN5)」の絞り込みトグルを追加!SPA対応・レイアウト崩れ対策・高速安定版。 // @description:en Adds a toggle in Amazon search results to filter for the official Amazon seller (p_6:AN1VRQENFRJN5). Supports SPA, layout fixes, and fast stable performance. // @description:zh-CN 在Amazon搜索结果中添加“官方卖家”筛选按钮(p_6:AN1VRQENFRJN5)。支持SPA、布局修复、快速稳定运行。 // @description:zh-TW 在Amazon搜尋結果中加入「官方賣家」篩選切換(p_6:AN1VRQENFRJN5)。支援SPA與版面修正,快速穩定。 // @description:ko 아마존 검색 결과에 공식 셀러(p_6:AN1VRQENFRJN5) 필터 토글 추가. SPA 대응, 레이아웃 문제 해결, 빠르고 안정적입니다. // @description:fr Ajoute un filtre pour le vendeur officiel Amazon dans les résultats de recherche. Compatible SPA, rapide et fiable. // @description:es Añade un filtro para el vendedor oficial de Amazon en los resultados de búsqueda. Compatible con SPA, estable y rápido. // @description:de Fügt in den Amazon-Suchergebnissen einen Filter für den offiziellen Verkäufer hinzu. Unterstützt SPA, schnelle und stabile Ausführung. // @description:pt-BR Adiciona um botão para filtrar pelo vendedor oficial da Amazon nos resultados de busca. Suporte a SPA, layout estável e rápido. // @description:ru Добавляет переключатель фильтра официального продавца Amazon в результатах поиска. Поддерживает SPA, исправления макета, быструю и стабильную работу. // @namespace https://github.com/koyasi777/amazon-official-seller-filter // @author koyasi777 // @match https://www.amazon.co.jp/s* // @match https://www.amazon.com/s* // @match https://www.amazon.co.uk/s* // @match https://www.amazon.de/s* // @match https://www.amazon.fr/s* // @match https://www.amazon.it/s* // @match https://www.amazon.es/s* // @match https://www.amazon.ca/s* // @match https://www.amazon.com.mx/s* // @match https://www.amazon.com.br/s* // @match https://www.amazon.in/s* // @match https://www.amazon.com.au/s* // @grant none // @license MIT // @homepageURL https://github.com/koyasi777/amazon-official-seller-filter // @supportURL https://github.com/koyasi777/amazon-official-seller-filter/issues // @downloadURL https://update.greasyfork.icu/scripts/533162/Amazon%E5%85%AC%E5%BC%8F%E3%82%BB%E3%83%A9%E3%83%BC%E7%B5%9E%E3%82%8A%E8%BE%BC%E3%81%BF%E3%83%88%E3%82%B0%E3%83%AB%20%F0%9F%94%84.user.js // @updateURL https://update.greasyfork.icu/scripts/533162/Amazon%E5%85%AC%E5%BC%8F%E3%82%BB%E3%83%A9%E3%83%BC%E7%B5%9E%E3%82%8A%E8%BE%BC%E3%81%BF%E3%83%88%E3%82%B0%E3%83%AB%20%F0%9F%94%84.meta.js // ==/UserScript== (() => { 'use strict'; const CONST = { P6_KEY: 'p_6:AN1VRQENFRJN5', STORAGE_KEY: 'p6_filter_enabled', WRAPPER_ID: 'p6-switch-wrapper', STYLE_ID: 'p6-style', TEMPLATE_ID: 'p6-template' }; const log = (...args) => console.debug('[p6-toggle]', ...args); const State = { get: () => localStorage.getItem(CONST.STORAGE_KEY) === 'true', set: (val) => localStorage.setItem(CONST.STORAGE_KEY, val ? 'true' : 'false'), }; const isP6InURL = () => new URLSearchParams(location.search).get('rh')?.includes(CONST.P6_KEY); const getKeyword = () => document.querySelector('input[name="field-keywords"]')?.value.trim() || ''; const navigateToSearch = (keyword, includeP6) => { const params = new URLSearchParams(location.search); params.delete('rh'); if (includeP6) params.set('rh', CONST.P6_KEY); if (keyword) params.set('k', keyword); params.set('timestamp', Date.now()); const newURL = `/s?${params.toString()}`; if (location.pathname + location.search !== newURL) location.assign(newURL); }; const insertStyle = () => { if (document.getElementById(CONST.STYLE_ID)) return; const style = document.createElement('style'); style.id = CONST.STYLE_ID; style.textContent = ` #${CONST.WRAPPER_ID} { display: flex; align-items: center; margin-left: 10px; gap: 8px; font-size: 13px; user-select: none; color: white; z-index: 9999; } .p6-label { font-weight: bold; } .p6-switch { position: relative; display: inline-block; width: 36px; height: 20px; } .p6-switch input { opacity: 0; width: 0; height: 0; } .p6-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #666; transition: .3s; border-radius: 34px; } .p6-circle { position: absolute; height: 14px; width: 14px; left: 4px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; } input:checked + .p6-slider { background-color: #4CAF50; } input:checked + .p6-slider .p6-circle { left: 18px; } `; document.head.appendChild(style); }; const insertTemplate = () => { if (document.getElementById(CONST.TEMPLATE_ID)) return; const tpl = document.createElement('template'); tpl.id = CONST.TEMPLATE_ID; tpl.innerHTML = `
`; document.body.appendChild(tpl); }; const createToggleUI = (enabled) => { const wrapper = document.getElementById(CONST.TEMPLATE_ID).content.firstElementChild.cloneNode(true); const checkbox = wrapper.querySelector('input'); const label = wrapper.querySelector('.p6-label'); const updateLabel = (on) => { label.textContent = `セラー絞り込み: ${on ? '有効中' : '無効'}`; label.style.color = on ? '#90ee90' : '#eee'; }; checkbox.checked = enabled; updateLabel(enabled); checkbox.addEventListener('change', () => { const newState = checkbox.checked; State.set(newState); updateLabel(newState); navigateToSearch(getKeyword(), newState); }); return wrapper; }; const mount = () => { try { const form = document.querySelector('#nav-search-bar-form'); const target = form?.querySelector('.nav-search-submit'); if (!form || !target || document.getElementById(CONST.WRAPPER_ID)) return; insertStyle(); insertTemplate(); const stateInURL = isP6InURL(); if (State.get() !== stateInURL) State.set(stateInURL); const toggle = createToggleUI(stateInURL); target.parentNode.insertBefore(toggle, target.nextSibling); const input = form.querySelector('input[name="field-keywords"]'); const submit = form.querySelector('.nav-search-submit input[type="submit"]'); const trigger = () => navigateToSearch(input.value.trim(), State.get()); form.addEventListener('submit', (e) => (e.preventDefault(), trigger())); submit.addEventListener('click', (e) => (e.preventDefault(), trigger())); input.addEventListener('keydown', (e) => e.key === 'Enter' && (e.preventDefault(), trigger())); } catch (e) { log('mount failed:', e); } }; const observeSuggestions = () => { const hooked = new WeakSet(); const observer = new MutationObserver(() => { document.querySelectorAll('.s-suggestion[role="button"]').forEach((el) => { if (hooked.has(el)) return; hooked.add(el); el.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const keyword = el.getAttribute('aria-label') || el.textContent.trim(); if (keyword) navigateToSearch(keyword, State.get()); }, { once: true }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }; const hookSPARouting = () => { const origPushState = history.pushState; history.pushState = function (...args) { const r = origPushState.apply(this, args); queueMicrotask(() => mount()); return r; }; window.addEventListener('popstate', () => queueMicrotask(() => mount())); }; const disableNavActive = () => { const form = document.querySelector('#nav-search-bar-form'); if (!form) return; const observer = new MutationObserver(() => { if (form.classList.contains('nav-active')) { form.classList.remove('nav-active'); log('Removed nav-active to prevent layout shift'); } }); observer.observe(form, { attributes: true, attributeFilter: ['class'] }); }; requestIdleCallback(() => { mount(); observeSuggestions(); hookSPARouting(); disableNavActive(); }); })();