您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动隐藏EXPO 2025搜索结果列表中不可预定的场馆
// ==UserScript== // @name 大阪世博会 EXPO 2025 空位预定助手 | 隐藏不可用场馆、自动加载更多、过滤特定场馆 // @namespace http://tampermonkey.net/ // @version 3.0 // @description 自动隐藏EXPO 2025搜索结果列表中不可预定的场馆 // @author You // @match https://ticket.expo2025.or.jp/* // @match https://www.expo2025.or.jp/* // @match https://expo2025.or.jp/* // @grant none // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; let debugMode = true; // 调试模式,默认开启以便诊断问题 let filterMode = 'hide-unavailable'; // 过滤模式: 'hide-unavailable', 'show-all', 'only-available', 'only-unavailable' let autoLoadMore = true; // 是否自动加载更多 let selectedVenues = new Set(); // 选中的场馆名称 let venueNameFilter = false; // 是否启用场馆名称过滤 function log(...args) { if (debugMode) { console.log('[EXPO隐藏脚本]', ...args); } } // 获取场馆状态类型 function getVenueStatus(element) { const icon = element.querySelector('img[src*="calendar"]'); if (icon) { const src = icon.getAttribute('src'); if (src && src.includes('calendar_none.svg')) { return 'unavailable'; } if (src && src.includes('calendar_few.svg')) { return 'crowded'; } if (src && src.includes('calendar_ok.svg')) { return 'available'; } } return 'unknown'; } // 检测元素是否为不可预定的场馆 function isUnavailableVenue(element) { return getVenueStatus(element) === 'unavailable'; } // 获取场馆名称 function getVenueName(element) { const titleElement = element.querySelector('.style_search_item_title__aePLg'); return titleElement ? titleElement.textContent.trim() : ''; } // 提取所有场馆名称 function getAllVenueNames() { const venueNames = new Set(); const elements = document.querySelectorAll('div.style_search_item_row__moqWC'); elements.forEach(element => { const button = element.querySelector('button[class*="search_item"]'); const icon = element.querySelector('img[src*="calendar"]'); if (button && icon && !element.querySelector('.style_more_btn__ymb22')) { const name = getVenueName(element); if (name) { // 清理场馆名称,去掉时间和特殊说明 let cleanName = name.replace(/[\d::~〜-]+\d+:\d+/g, '') // 去掉时间 .replace(/\s*~.*$/, '') // 去掉~后面的内容 .replace(/\s*\(.*?\)/g, '') // 去掉括号内容 .replace(/\s*【.*?】/g, '') // 去掉【】内容 .replace(/\s*※.*$/, '') // 去掉※后面的内容 .replace(/\s*\*.*$/, '') // 去掉*后面的内容 .replace(/\s+/g, ' ') // 合并多个空格 .trim(); if (cleanName) { venueNames.add(cleanName); } } } }); return Array.from(venueNames).sort(); } // 应用过滤模式 function applyFilter() { const elements = document.querySelectorAll('div.style_search_item_row__moqWC'); let stats = { available: 0, crowded: 0, unavailable: 0, total: 0, shown: 0, hidden: 0 }; log('开始应用过滤,找到元素数量:', elements.length); elements.forEach(element => { // 确保是场馆行,必须包含按钮和图标 const button = element.querySelector('button[class*="search_item"]'); const icon = element.querySelector('img[src*="calendar"]'); if (!button || !icon) { log('跳过非场馆元素(无按钮或图标):', element); return; } // 跳过"查看更多"按钮的容器 if (element.querySelector('.style_more_btn__ymb22')) { log('跳过"查看更多"按钮容器'); return; } const status = getVenueStatus(element); stats.total++; // 统计各状态数量 if (status === 'available') stats.available++; else if (status === 'crowded') stats.crowded++; else if (status === 'unavailable') stats.unavailable++; let shouldShow = false; // 首先按状态过滤 switch (filterMode) { case 'show-all': shouldShow = true; break; case 'hide-unavailable': shouldShow = status !== 'unavailable'; break; case 'only-available': shouldShow = status === 'available'; break; case 'only-crowded': shouldShow = status === 'crowded'; break; case 'only-unavailable': shouldShow = status === 'unavailable'; break; } // 然后按场馆名称过滤 if (shouldShow && venueNameFilter && selectedVenues.size > 0) { const venueName = getVenueName(element); const cleanName = venueName.replace(/[\d::~〜-]+\d+:\d+/g, '') .replace(/\s*~.*$/, '') .replace(/\s*\(.*?\)/g, '') .replace(/\s*【.*?】/g, '') .replace(/\s*※.*$/, '') .replace(/\s*\*.*$/, '') .replace(/\s+/g, ' ') .trim(); shouldShow = selectedVenues.has(cleanName); } element.style.display = shouldShow ? '' : 'none'; if (shouldShow) { stats.shown++; } else { stats.hidden++; } }); log(`过滤统计 - 总计: ${stats.total}, 空闲: ${stats.available}, 拥挤: ${stats.crowded}, 已满: ${stats.unavailable}, 显示: ${stats.shown}, 隐藏: ${stats.hidden}`); return stats; } // 自动点击"查看更多"按钮 function autoClickLoadMore() { if (!autoLoadMore) return; const loadMoreBtn = document.querySelector('.style_more_btn__ymb22'); if (loadMoreBtn && loadMoreBtn.offsetParent !== null) { log('自动点击"查看更多"按钮'); loadMoreBtn.click(); // 等待新内容加载后重新应用过滤 setTimeout(() => { applyFilter(); // 更新场馆列表(如果启用了场馆过滤) if (venueNameFilter && window.controlPanelInstance) { window.controlPanelInstance.updateVenueList(); } // 继续查找是否还有更多 setTimeout(autoClickLoadMore, 1000); }, 1500); } else { log('没有找到"查看更多"按钮或已全部加载'); } } // 创建控制面板 function createControlPanel() { // 创建容器 const container = document.createElement('div'); container.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 9999; background: white; border: 2px solid #ddd; border-radius: 10px; padding: 15px; box-shadow: 0 4px 10px rgba(0,0,0,0.3); font-family: Arial, sans-serif; min-width: 250px; `; // 标题 const title = document.createElement('h3'); title.textContent = 'EXPO 场馆过滤器'; title.style.cssText = ` margin: 0 0 10px 0; font-size: 16px; color: #333; text-align: center; `; // 统计信息 const statsDiv = document.createElement('div'); statsDiv.style.cssText = ` margin-bottom: 10px; padding: 5px; background: #f5f5f5; border-radius: 5px; font-size: 12px; text-align: center; `; // 状态过滤模式选择 const filterLabel = document.createElement('label'); filterLabel.textContent = '状态过滤:'; filterLabel.style.cssText = ` display: block; margin-bottom: 5px; font-size: 14px; font-weight: bold; `; const filterSelect = document.createElement('select'); filterSelect.style.cssText = ` width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 3px; `; const filterOptions = [ { value: 'show-all', text: '显示全部' }, { value: 'hide-unavailable', text: '隐藏已满场馆' }, { value: 'only-available', text: '只显示空闲场馆 🟢' }, { value: 'only-crowded', text: '只显示拥挤场馆 🟡' }, { value: 'only-unavailable', text: '只显示已满场馆 🔴' } ]; filterOptions.forEach(option => { const opt = document.createElement('option'); opt.value = option.value; opt.textContent = option.text; if (option.value === filterMode) opt.selected = true; filterSelect.appendChild(opt); }); filterSelect.addEventListener('change', () => { filterMode = filterSelect.value; const stats = applyFilter(); updateStats(stats); log('切换过滤模式到:', filterMode); }); // 场馆名称过滤 const venueFilterLabel = document.createElement('label'); venueFilterLabel.style.cssText = ` display: flex; align-items: center; margin-bottom: 10px; cursor: pointer; `; const venueFilterCheckbox = document.createElement('input'); venueFilterCheckbox.type = 'checkbox'; venueFilterCheckbox.checked = venueNameFilter; venueFilterCheckbox.style.marginRight = '5px'; const venueFilterText = document.createElement('span'); venueFilterText.textContent = '按场馆名称过滤'; venueFilterText.style.fontSize = '12px'; venueFilterLabel.appendChild(venueFilterCheckbox); venueFilterLabel.appendChild(venueFilterText); // 场馆选择区域 const venueSelectDiv = document.createElement('div'); venueSelectDiv.style.cssText = ` max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 3px; padding: 5px; margin-bottom: 10px; background: #f9f9f9; display: ${venueNameFilter ? 'block' : 'none'}; `; // 全选/取消全选按钮 const selectAllDiv = document.createElement('div'); selectAllDiv.style.cssText = ` margin-bottom: 5px; text-align: center; `; const selectAllBtn = document.createElement('button'); selectAllBtn.textContent = '全选'; selectAllBtn.style.cssText = ` padding: 3px 8px; margin-right: 5px; background: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; `; const deselectAllBtn = document.createElement('button'); deselectAllBtn.textContent = '取消全选'; deselectAllBtn.style.cssText = ` padding: 3px 8px; background: #999; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; `; selectAllDiv.appendChild(selectAllBtn); selectAllDiv.appendChild(deselectAllBtn); venueSelectDiv.appendChild(selectAllDiv); // 更新场馆列表 function updateVenueList() { // 清除现有列表(保留全选按钮) const checkboxes = venueSelectDiv.querySelectorAll('label'); checkboxes.forEach(label => label.remove()); const venueNames = getAllVenueNames(); venueNames.forEach(name => { const label = document.createElement('label'); label.style.cssText = ` display: block; margin-bottom: 3px; cursor: pointer; font-size: 11px; `; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = selectedVenues.has(name); checkbox.style.marginRight = '5px'; checkbox.addEventListener('change', () => { if (checkbox.checked) { selectedVenues.add(name); } else { selectedVenues.delete(name); } const stats = applyFilter(); updateStats(stats); }); const span = document.createElement('span'); span.textContent = name; label.appendChild(checkbox); label.appendChild(span); venueSelectDiv.appendChild(label); }); } // 全选事件 selectAllBtn.addEventListener('click', () => { const venueNames = getAllVenueNames(); venueNames.forEach(name => selectedVenues.add(name)); updateVenueList(); const stats = applyFilter(); updateStats(stats); }); // 取消全选事件 deselectAllBtn.addEventListener('click', () => { selectedVenues.clear(); updateVenueList(); const stats = applyFilter(); updateStats(stats); }); // 场馆过滤开关事件 venueFilterCheckbox.addEventListener('change', () => { venueNameFilter = venueFilterCheckbox.checked; venueSelectDiv.style.display = venueNameFilter ? 'block' : 'none'; if (venueNameFilter) { updateVenueList(); } const stats = applyFilter(); updateStats(stats); }); // 自动加载开关 const autoLoadLabel = document.createElement('label'); autoLoadLabel.style.cssText = ` display: flex; align-items: center; margin-bottom: 10px; cursor: pointer; `; const autoLoadCheckbox = document.createElement('input'); autoLoadCheckbox.type = 'checkbox'; autoLoadCheckbox.checked = autoLoadMore; autoLoadCheckbox.style.marginRight = '5px'; autoLoadCheckbox.addEventListener('change', () => { autoLoadMore = autoLoadCheckbox.checked; log('自动加载更多:', autoLoadMore ? '开启' : '关闭'); if (autoLoadMore) { setTimeout(autoClickLoadMore, 1000); } }); const autoLoadText = document.createElement('span'); autoLoadText.textContent = '自动点击"查看更多"'; autoLoadText.style.fontSize = '12px'; autoLoadLabel.appendChild(autoLoadCheckbox); autoLoadLabel.appendChild(autoLoadText); // 手动加载按钮 const loadMoreBtn = document.createElement('button'); loadMoreBtn.textContent = '手动点击"查看更多"'; loadMoreBtn.style.cssText = ` width: 100%; padding: 8px; background-color: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 12px; margin-bottom: 5px; `; loadMoreBtn.addEventListener('click', () => { const btn = document.querySelector('.style_more_btn__ymb22'); if (btn && btn.offsetParent !== null) { btn.click(); setTimeout(() => { const stats = applyFilter(); updateStats(stats); // 更新场馆列表 if (venueNameFilter) { updateVenueList(); } }, 1500); } else { alert('没有找到"查看更多"按钮'); } }); // 调试模式开关 const debugBtn = document.createElement('button'); debugBtn.textContent = debugMode ? '关闭调试' : '开启调试'; debugBtn.style.cssText = ` width: 100%; padding: 5px; background-color: #FF9800; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; `; debugBtn.addEventListener('click', () => { debugMode = !debugMode; debugBtn.textContent = debugMode ? '关闭调试' : '开启调试'; }); // 更新统计信息 function updateStats(stats) { log('更新统计信息:', stats); if (stats && typeof stats === 'object' && stats.total !== undefined) { statsDiv.innerHTML = ` 总计: ${stats.total} | 🟢 ${stats.available} | 🟡 ${stats.crowded} | 🔴 ${stats.unavailable} <br>显示: ${stats.shown || 0} | 隐藏: ${stats.hidden || 0} `; } else { statsDiv.innerHTML = '统计加载中...'; log('统计数据无效:', stats); } } // 组装面板 container.appendChild(title); container.appendChild(statsDiv); container.appendChild(filterLabel); container.appendChild(filterSelect); container.appendChild(venueFilterLabel); container.appendChild(venueSelectDiv); container.appendChild(autoLoadLabel); container.appendChild(loadMoreBtn); container.appendChild(debugBtn); document.body.appendChild(container); // 初始化统计 setTimeout(() => { log('开始初始化统计'); const initialStats = applyFilter(); log('初始化统计结果:', initialStats); updateStats(initialStats); if (venueNameFilter) { updateVenueList(); } }, 1000); return { updateStats, updateVenueList }; } // 等待元素出现 function waitForElements(timeout = 10000) { const startTime = Date.now(); function checkElements() { const hasContent = document.querySelector('main') && document.querySelector('main').children.length > 0; if (hasContent) { log('页面内容已加载'); applyFilter(); return true; } if (Date.now() - startTime < timeout) { setTimeout(checkElements, 200); } else { log('等待超时,尝试执行过滤功能'); applyFilter(); } } checkElements(); } // 使用MutationObserver监听DOM变化 function observeChanges() { let debounceTimer; const observer = new MutationObserver((mutations) => { // 使用防抖来避免过多执行 clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { log('检测到DOM变化,重新执行过滤功能'); applyFilter(); }, 300); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'disabled', 'aria-disabled', 'data-available', 'data-status'] }); log('MutationObserver 已启动'); } // 初始化函数 function init() { log('脚本开始执行'); // 创建控制面板 window.controlPanelInstance = createControlPanel(); // 等待元素加载 waitForElements(); // 启动观察器 setTimeout(() => { observeChanges(); }, 1000); // 启动自动加载更多 setTimeout(() => { if (autoLoadMore) { autoClickLoadMore(); } }, 3000); // 定期检查(针对某些特殊情况) setInterval(() => { if (document.hidden) return; // 页面不可见时跳过 applyFilter(); }, 5000); } // 页面加载完成后执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { // 如果页面已经加载,等待一下让React/Next.js完成渲染 setTimeout(init, 500); } })();