// ==UserScript==
// @name Amazon公式セラー絞り込みトグル 🔄(p_6:AN1VRQENFRJN5対応)
// @namespace https://github.com/koyasi777/amazon-seller-filter-toggle
// @version 14.1
// @description Amazon検索結果に「Amazon公式セラー(p_6:AN1VRQENFRJN5)」の絞り込みトグルを追加!SPA対応・レイアウト崩れ対策・高速安定版。
// @author koyasi777
// @match https://www.amazon.co.jp/s*
// @grant none
// @license MIT
// @homepageURL https://github.com/koyasi777/amazon-seller-filter-toggle
// @supportURL https://github.com/koyasi777/amazon-seller-filter-toggle/issues
// @downloadURL none
// ==/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();
});
})();