您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
SUUMOの検索結果(建物ごとに表示)で「非表示」ボタンから不要な物件を隠せる!モーダルUIから復活も簡単。保存はローカル。
// ==UserScript== // @name SUUMO物件非表示マネージャ 🏠 // @name:ja SUUMO物件非表示マネージャ 🏠 // @name:en SUUMO Hidden Property Manager 🏠 // @version 3.7.1 // @description SUUMOの検索結果(建物ごとに表示)で「非表示」ボタンから不要な物件を隠せる!モーダルUIから復活も簡単。保存はローカル。 // @description:ja SUUMOの検索結果(建物ごとに表示)で「非表示」ボタンから不要な物件を隠せる!モーダルUIから復活も簡単。保存はローカル。 // @description:en Hide unwanted listings in SUUMO's grouped-by-building search results! Restore via modal UI. Data saved locally. // @namespace https://github.com/koyasi777/suumo-hidden-property-manager // @author koyasi777 // @match https://suumo.jp/jj/chintai/ichiran/FR* // @grant GM_addStyle // @run-at document-idle // @license MIT // @homepageURL https://github.com/koyasi777/suumo-hidden-property-manager // @supportURL https://github.com/koyasi777/suumo-hidden-property-manager/issues // @icon https://suumo.jp/front/img/favicon.ico // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY = 'suumoHiddenProperties'; const PROPERTY_LI_PREFIX = 'property-li-'; console.log('SUUMO非表示スクリプト: 実行開始'); // --- データ管理ロジック (変更なし) --- const storage = { get: () => JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'), save: (data) => localStorage.setItem(STORAGE_KEY, JSON.stringify(data)), add: (property) => { const properties = storage.get(); if (!properties.some(p => p.id === property.id)) { properties.push(property); storage.save(properties); } }, remove: (propertyId) => { let properties = storage.get(); properties = properties.filter(p => p.id !== propertyId); storage.save(properties); }, clear: () => localStorage.removeItem(STORAGE_KEY) }; // --- UI/DOM操作 --- const ui = { injectCSS: () => { GM_addStyle(` /* ページスクロールロック用クラス */ body.hide-modal-open { overflow: hidden; } .suumo-control-button { padding: 8px 14px; color: white !important; border: none; cursor: pointer; border-radius: 4px; font-size: 14px; font-weight: bold; font-family: "メイリオ", Meiryo, "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "MS Pゴシック", sans-serif; line-height: 1.2; text-decoration: none !important; display: inline-block; white-space: nowrap; box-shadow: 0 2px 4px rgba(0,0,0,0.15); transition: all 0.15s ease-in-out; } .suumo-control-button:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); filter: brightness(1.1); } .suumo-control-button:active { transform: translateY(1px); box-shadow: 0 1px 2px rgba(0,0,0,0.2); filter: brightness(0.95); } .suumo-control-button--small { padding: 5px 10px; font-size: 12px; font-weight: normal; } .hide-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9998; display: none; } .hide-modal-content { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; z-index: 9999; padding: 20px 30px; border-radius: 8px; width: 90%; max-width: 600px; max-height: 80vh; box-shadow: 0 5px 15px rgba(0,0,0,0.3); display: none; flex-direction: column; overflow: hidden; } .hide-modal-header { display: flex; justify-content: flex-start; align-items: center; flex-shrink: 0; border-bottom: 1px solid #ccc; padding-bottom: 10px; } .hide-modal-header h2 { margin: 0; font-family: "メイリオ", Meiryo, sans-serif; } .hide-modal-header .suumo-control-button { margin-left: 15px; } .hide-modal-close { position: absolute; top: 15px; right: 20px; font-size: 24px; font-weight: bold; cursor: pointer; color: #aaa; z-index: 10; } .hide-modal-body { flex-grow: 1; overflow-y: auto; margin-top: 15px; min-height: 0; } .hidden-property-list { list-style: none; padding: 0; } .hidden-property-list li { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee; } .hidden-property-info a { text-decoration: none; color: #0073e6; font-weight: bold; font-family: "メイリオ", Meiryo, sans-serif;} .hidden-property-info span { display: block; font-size: 0.9em; color: #555; font-family: "メイリオ", Meiryo, sans-serif;} .inquiry.inquiry--top { display: flex; justify-content: space-between; align-items: center; padding-right: 10px; } `); }, createControls: () => { if (document.getElementById('suumo-hide-controls')) return; const targetParent = document.querySelector('.inquiry.inquiry--top'); if (!targetParent) return; const manageButton = document.createElement('button'); manageButton.type = 'button'; manageButton.id = 'suumo-hide-controls'; manageButton.textContent = '非表示リスト管理'; manageButton.className = 'suumo-control-button'; manageButton.style.backgroundColor = '#5bc0de'; manageButton.addEventListener('click', () => ui.toggleModal(true)); targetParent.appendChild(manageButton); }, createModal: () => { if (document.getElementById('hide-modal')) return; document.body.insertAdjacentHTML('beforeend', ` <div id="hide-modal-overlay" class="hide-modal-overlay"></div> <div id="hide-modal-content" class="hide-modal-content"> <span class="hide-modal-close">×</span> <div class="hide-modal-header"> <h2>非表示物件リスト</h2> <button id="restore-all-btn" class="suumo-control-button suumo-control-button--small" style="background-color: #f0ad4e;">全復活</button> </div> <div class="hide-modal-body"> <ul id="hidden-property-list" class="hidden-property-list"></ul> </div> </div> `); document.getElementById('hide-modal-overlay').addEventListener('click', () => ui.toggleModal(false)); document.querySelector('#hide-modal-content .hide-modal-close').addEventListener('click', () => ui.toggleModal(false)); const restoreAllBtn = document.getElementById('restore-all-btn'); restoreAllBtn.type = 'button'; restoreAllBtn.addEventListener('click', () => { if (confirm('非表示中の全ての物件を復活させますか?')) { const hiddenProperties = storage.get(); if(hiddenProperties.length === 0) { alert('非表示の物件はありません。'); return; } hiddenProperties.forEach(p => ui.restoreProperty(p.id)); storage.clear(); ui.populateModal(); alert(`${hiddenProperties.length}件の物件を全て復活させました。`); } }); }, populateModal: () => { const listElement = document.getElementById('hidden-property-list'); const hiddenProperties = storage.get(); listElement.innerHTML = ''; if (hiddenProperties.length === 0) { listElement.innerHTML = '<li>非表示中の物件はありません。</li>'; return; } hiddenProperties.forEach(prop => { const listItem = document.createElement('li'); listItem.id = `hidden-item-${prop.id}`; listItem.innerHTML = `<div class="hidden-property-info"><a href="${prop.url}" target="_blank" rel="noopener noreferrer">${prop.name}</a><span>${prop.rent}</span></div>`; const restoreButton = document.createElement('button'); restoreButton.type = 'button'; restoreButton.textContent = '復活'; restoreButton.className = 'suumo-control-button'; restoreButton.style.backgroundColor = '#5cb85c'; restoreButton.addEventListener('click', () => { storage.remove(prop.id); ui.restoreProperty(prop.id); listItem.remove(); if (document.querySelectorAll('#hidden-property-list li').length === 0) { listElement.innerHTML = '<li>非表示中の物件はありません。</li>'; } }); listItem.appendChild(restoreButton); listElement.appendChild(listItem); }); }, /* ===== ★スクロールロック機能を実装 ===== */ toggleModal: (show) => { const overlay = document.getElementById('hide-modal-overlay'); const content = document.getElementById('hide-modal-content'); if (show) { document.body.classList.add('hide-modal-open'); // ページスクロールをロック ui.populateModal(); overlay.style.display = 'block'; content.style.display = 'flex'; // display:flexに戻す } else { document.body.classList.remove('hide-modal-open'); // ページスクロールのロックを解除 overlay.style.display = 'none'; content.style.display = 'none'; } }, restoreProperty: (propertyId) => { const propertyLi = document.getElementById(PROPERTY_LI_PREFIX + propertyId); if (propertyLi) { propertyLi.style.transition = 'opacity 0.4s ease, transform 0.4s ease'; propertyLi.style.display = 'block'; setTimeout(() => { propertyLi.style.opacity = '1'; propertyLi.style.transform = 'scale(1)'; }, 10); } } }; // --- メイン処理ロジック --- function processPropertyItems() { const hiddenIds = storage.get().map(p => p.id); hiddenIds.forEach(hiddenId => { const propLi = document.getElementById(PROPERTY_LI_PREFIX + hiddenId); if (propLi && propLi.style.display !== 'none') { propLi.style.display = 'none'; propLi.style.opacity = '0'; } }); const propertyItems = document.querySelectorAll('.cassetteitem:not(.hide-processed)'); propertyItems.forEach(item => { item.classList.add('hide-processed'); const checkbox = item.querySelector('input.js-single_checkbox[type="checkbox"]'); const bukkenId = checkbox ? checkbox.value : null; if (!bukkenId) return; const parentLi = item.closest('li'); if (!parentLi) return; parentLi.id = PROPERTY_LI_PREFIX + bukkenId; if (hiddenIds.includes(bukkenId)) { if (parentLi.style.display !== 'none') { parentLi.style.display = 'none'; parentLi.style.opacity = '0'; } return; } const buttonContainer = item.querySelector('.cassetteitem_other-col09'); if (buttonContainer && !buttonContainer.querySelector('.hide-property-button')) { const hideButton = document.createElement('button'); hideButton.type = 'button'; hideButton.textContent = '非表示'; hideButton.className = 'suumo-control-button hide-property-button'; hideButton.style.cssText = 'background-color: #d9534f; margin-top: 5px;'; hideButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const name = item.querySelector('.cassetteitem_content-title')?.textContent.trim() || '物件名不明'; const rent = item.querySelector('.cassetteitem_price--rent')?.textContent.trim() || '賃料不明'; const url = item.querySelector('.js-cassette_link_href')?.href || '#'; if (confirm(`【${name}】を非表示にしますか?`)) { storage.add({ id: bukkenId, name, rent, url }); parentLi.style.transition = 'opacity 0.4s ease, transform 0.4s ease'; parentLi.style.opacity = '0'; parentLi.style.transform = 'scale(0.95)'; setTimeout(() => { parentLi.style.display = 'none'; }, 400); } }); buttonContainer.appendChild(hideButton); } }); } // --- 初期化と監視 --- function init() { ui.injectCSS(); ui.createModal(); const observer = new MutationObserver(() => { if (!document.getElementById('suumo-hide-controls')) { ui.createControls(); } processPropertyItems(); }); observer.observe(document.body, { childList: true, subtree: true }); ui.createControls(); processPropertyItems(); } if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { document.addEventListener('DOMContentLoaded', init); } })();