您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlights profiles on Bumble based on various criteria (Nice, Neutral, No-Go) with an interactive settings panel, radio button selection, and specific handling for height.
// ==UserScript== // @name Bumble Filter (Interactive Settings with Radio Buttons & Number Filter) // @namespace http://tampermonkey.net/ // @version 0.9.1 // @description Highlights profiles on Bumble based on various criteria (Nice, Neutral, No-Go) with an interactive settings panel, radio button selection, and specific handling for height. // @author Your Name // @match *://*.bumble.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- GLOBAL VARIABLES AND DEFAULT CONFIGURATION --- const SETTINGS_KEY = 'bumbleFilterSettings_v0_9_1'; // Updated version key const HIGHLIGHT_COLOR_NICE = 'lightgreen'; const HIGHLIGHT_COLOR_INDIFFERENT = '#f0f0f0'; // Light gray for "neutral" const HIGHLIGHT_COLOR_NO_GO = '#ffcccb'; const defaultSettingsTemplate = { education: { imageSubstring: 'education', desired: [], noGo: [], label: "Education", allKnownValues: [], type: "radio" }, familyplans: { imageSubstring: 'familyplans', desired: [], noGo: [], label: "Family Plans", allKnownValues: [], type: "radio" }, height: { imageSubstring: 'height', desiredMinCm: null, desiredMaxCm: null, noGoSpecificCm: [], label: "Height", allKnownValues: [], type: "numberRange" }, exercise: { imageSubstring: 'exercise', desired: [], noGo: [], label: "Exercise", allKnownValues: [], type: "radio" }, drinking: { imageSubstring: 'drinking', desired: [], noGo: [], label: "Drinking Habits", allKnownValues: [], type: "radio" }, smoking: { imageSubstring: 'smoking', desired: [], noGo: [], label: "Smoking Habits", allKnownValues: [], type: "radio" }, cannabis: { imageSubstring: 'cannabis', desired: [], noGo: [], label: "Cannabis Use", allKnownValues: [], type: "radio" }, religion: { imageSubstring: 'religion', desired: [], noGo: [], label: "Religion", allKnownValues: [], type: "radio" }, intentions: { imageSubstring: 'intentions', desired: [], noGo: [], label: "Intentions", allKnownValues: [], type: "radio" }, politics: { imageSubstring: 'politics', desired: [], noGo: [], label: "Politics", allKnownValues: [], type: "radio" }, starsign: { imageSubstring: 'starsign', desired: [], noGo: [], label: "Star Sign", allKnownValues: [], type: "radio" } }; let currentSettings = {}; const profileCardSelectors = ['.encounters-story']; function parseCmValue(cmString) { if (typeof cmString !== 'string') return null; const match = cmString.match(/(\d+)/); return match ? parseInt(match[1], 10) : null; } function loadSettings() { console.log("[Bumble Filter] Loading settings..."); const storedSettings = GM_getValue(SETTINGS_KEY); currentSettings = JSON.parse(JSON.stringify(defaultSettingsTemplate)); if (storedSettings) { console.log("[Bumble Filter] Found stored settings:", JSON.parse(JSON.stringify(storedSettings))); for (const key in currentSettings) { if (storedSettings[key]) { const categoryTemplate = defaultSettingsTemplate[key]; const storedCategory = storedSettings[key]; if (categoryTemplate.type === "numberRange") { currentSettings[key].desiredMinCm = storedCategory.desiredMinCm !== undefined ? (parseInt(String(storedCategory.desiredMinCm), 10) || null) : categoryTemplate.desiredMinCm; currentSettings[key].desiredMaxCm = storedCategory.desiredMaxCm !== undefined ? (parseInt(String(storedCategory.desiredMaxCm), 10) || null) : categoryTemplate.desiredMaxCm; currentSettings[key].noGoSpecificCm = Array.isArray(storedCategory.noGoSpecificCm) ? storedCategory.noGoSpecificCm.map(s => parseInt(String(s), 10)).filter(n => !isNaN(n)) : categoryTemplate.noGoSpecificCm; } else { currentSettings[key].desired = Array.isArray(storedCategory.desired) ? storedCategory.desired.map(s => String(s).toLowerCase().trim()).filter(s => s) : categoryTemplate.desired; currentSettings[key].noGo = Array.isArray(storedCategory.noGo) ? storedCategory.noGo.map(s => String(s).toLowerCase().trim()).filter(s => s) : categoryTemplate.noGo; } currentSettings[key].allKnownValues = Array.isArray(storedCategory.allKnownValues) ? storedCategory.allKnownValues.map(s => String(s).toLowerCase().trim()).filter(s => s) : []; } } } for (const key in currentSettings) { const category = currentSettings[key]; if (category.type === "radio") { const knownValuesSet = new Set(category.allKnownValues); category.desired.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim())); category.noGo.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim())); category.allKnownValues = Array.from(knownValuesSet).sort(); } } console.log("[Bumble Filter] Settings loaded and merged:", JSON.parse(JSON.stringify(currentSettings))); } function saveSettings() { console.log("[Bumble Filter] Attempting to save settings. Current settings object before reading DOM:", JSON.parse(JSON.stringify(currentSettings))); for (const categoryKey in currentSettings) { const category = currentSettings[categoryKey]; const panelContent = document.getElementById('bf-panel-content'); if (!panelContent) { console.error("[Bumble Filter] Panel content not found for saving."); continue; } console.log(`[Bumble Filter] Saving category: ${categoryKey}`); if (category.type === "numberRange") { const minInput = panelContent.querySelector(`#bf-desiredMinCm-${categoryKey}`); const maxInput = panelContent.querySelector(`#bf-desiredMaxCm-${categoryKey}`); const noGoInput = panelContent.querySelector(`#bf-noGoSpecificCm-${categoryKey}`); category.desiredMinCm = minInput && minInput.value.trim() !== "" ? parseInt(minInput.value, 10) : null; category.desiredMaxCm = maxInput && maxInput.value.trim() !== "" ? parseInt(maxInput.value, 10) : null; category.noGoSpecificCm = noGoInput ? noGoInput.value.split('\n').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n)) : []; } else { const newDesired = []; const newNoGo = []; category.allKnownValues.forEach((value, index) => { const stringValue = String(value); const radioGroupName = `bf-state-${categoryKey}-${index}`; const checkedRadio = panelContent.querySelector(`input[name="${radioGroupName}"]:checked`); if (checkedRadio) { if (checkedRadio.value === 'nice') { newDesired.push(stringValue); } else if (checkedRadio.value === 'nogo') { newNoGo.push(stringValue); } } }); category.desired = newDesired; category.noGo = newNoGo; console.log(`[Bumble Filter] Category ${categoryKey} after DOM read - Desired:`, [...category.desired], "NoGo:", [...category.noGo]); } } GM_setValue(SETTINGS_KEY, currentSettings); console.log("[Bumble Filter] Settings saved to GM_setValue:", JSON.parse(JSON.stringify(currentSettings))); document.querySelectorAll('[data-custom-filter-processed-v9-1]').forEach(card => { card.removeAttribute('data-custom-filter-processed-v9-1'); }); processVisibleProfileCards(); } function resetSettings() { console.log("[Bumble Filter] Resetting settings to default."); if (confirm("Do you really want to reset all filter settings to their default values?")) { GM_setValue(SETTINGS_KEY, undefined); loadSettings(); const panel = document.getElementById('bumble-filter-settings-panel'); if (panel && panel.style.display === 'block') { buildSettingsPanelContent(panel.querySelector('#bf-panel-content')); } document.querySelectorAll('[data-custom-filter-processed-v9-1]').forEach(card => { card.removeAttribute('data-custom-filter-processed-v9-1'); }); processVisibleProfileCards(); alert("Settings have been reset."); } } function buildSettingsPanelContent(panelContentDiv) { if (!panelContentDiv) { console.error("[Bumble Filter] buildSettingsPanelContent: panelContentDiv is null"); return; } for (const key in currentSettings) { const category = currentSettings[key]; if (category.type === "radio") { const knownValuesSet = new Set(category.allKnownValues); currentSettings[key].desired.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim())); currentSettings[key].noGo.forEach(val => knownValuesSet.add(String(val).toLowerCase().trim())); category.allKnownValues = Array.from(knownValuesSet).sort(); } } console.log("[Bumble Filter] Building panel content with current settings for UI:", JSON.parse(JSON.stringify(currentSettings))); let contentHtml = ''; for (const categoryKey in currentSettings) { const category = currentSettings[categoryKey]; contentHtml += `<div class="bf-category-section"><h3>${category.label} (ID: ${category.imageSubstring})</h3>`; if (category.type === "numberRange") { contentHtml += ` <div class="bf-input-group"> <label for="bf-desiredMinCm-${categoryKey}">Min. desired height (cm):</label> <input type="number" id="bf-desiredMinCm-${categoryKey}" value="${category.desiredMinCm === null ? '' : category.desiredMinCm}" placeholder="e.g. 170"> </div> <div class="bf-input-group"> <label for="bf-desiredMaxCm-${categoryKey}">Max. desired height (cm):</label> <input type="number" id="bf-desiredMaxCm-${categoryKey}" value="${category.desiredMaxCm === null ? '' : category.desiredMaxCm}" placeholder="e.g. 185"> </div> <div class="bf-input-group"> <label for="bf-noGoSpecificCm-${categoryKey}">No-Go heights (cm, per line):</label> <textarea id="bf-noGoSpecificCm-${categoryKey}" placeholder="e.g. 160\n195">${category.noGoSpecificCm.join('\n')}</textarea> </div>`; if (category.allKnownValues.length > 0) { contentHtml += `<p class="bf-info-text">Discovered heights (examples): ${category.allKnownValues.map(v => parseCmValue(v) + 'cm').filter(v => v !== 'nullcm').slice(0,10).join(', ')}${category.allKnownValues.length > 10 ? '...' : ''}</p>`; } } else { if (category.allKnownValues.length === 0) { contentHtml += `<p class="bf-no-values">No values discovered for this category yet. They will be collected as you swipe.</p>`; } const sortedKnownValues = [...category.allKnownValues].sort(); sortedKnownValues.forEach((value, index) => { const stringValue = String(value); const isDesired = category.desired.includes(stringValue); const isNoGo = category.noGo.includes(stringValue); const isNeutral = !isDesired && !isNoGo; const radioGroupName = `bf-state-${categoryKey}-${index}`; contentHtml += ` <div class="bf-value-row"> <span class="bf-value-label" title="${stringValue}">${stringValue.length > 25 ? stringValue.substring(0, 22) + '...' : stringValue}</span> <div class="bf-radio-group"> <label class="bf-radio-label"><input type="radio" name="${radioGroupName}" value="nice" ${isDesired ? 'checked' : ''}> Nice</label> <label class="bf-radio-label"><input type="radio" name="${radioGroupName}" value="neutral" ${isNeutral ? 'checked' : ''}> Neutral</label> <label class="bf-radio-label"><input type="radio" name="${radioGroupName}" value="nogo" ${isNoGo ? 'checked' : ''}> No-Go</label> </div> </div>`; }); } contentHtml += `</div>`; } panelContentDiv.innerHTML = contentHtml; } function createSettingsPanelShell() { let panel = document.getElementById('bumble-filter-settings-panel'); if (panel) return panel; console.log("[Bumble Filter] Creating settings panel shell for the first time."); let panelHtml = ` <div id="bf-panel-header"> <h2>Bumble Filter Settings</h2> <button id="bf-close-panel" title="Close">X</button> </div> <div id="bf-panel-content"></div> <div id="bf-panel-footer"> <button id="bf-reset-settings" title="Reset all settings to default">Reset</button> <button id="bf-save-settings">Save & Apply</button> </div>`; panel = document.createElement('div'); panel.id = 'bumble-filter-settings-panel'; panel.innerHTML = panelHtml; document.body.appendChild(panel); document.getElementById('bf-save-settings').addEventListener('click', () => saveSettings()); document.getElementById('bf-close-panel').addEventListener('click', () => { const panelToClose = document.getElementById('bumble-filter-settings-panel'); if(panelToClose) panelToClose.style.display = 'none'; }); document.getElementById('bf-reset-settings').addEventListener('click', () => resetSettings()); return panel; } function addStyles() { GM_addStyle(` #bumble-filter-settings-panel { position: fixed; top: 50px; right: 20px; width: 480px; max-height: 85vh; background-color: white; border: 1px solid #ccc; box-shadow: 0 0 15px rgba(0,0,0,0.2); z-index: 100000 !important; display: none; font-family: Arial, sans-serif; font-size: 14px; border-radius: 8px; pointer-events: auto !important; } #bf-panel-header { background-color: #f0f0f0; padding: 10px 15px; border-bottom: 1px solid #ccc; display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 8px; border-top-right-radius: 8px; } #bf-panel-header h2 { margin: 0; font-size: 16px; } #bf-close-panel, #bf-save-settings, #bf-open-settings-button, #bf-reset-settings { pointer-events: auto !important; cursor: pointer; } #bf-close-panel { background: none; border: none; font-size: 20px; padding: 0 5px;} #bf-panel-content { padding: 15px; overflow-y: auto; max-height: calc(85vh - 100px); } .bf-category-section { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .bf-category-section:last-child { border-bottom: none; margin-bottom: 0; } .bf-category-section h3 { font-size: 14px; margin-top: 0; margin-bottom: 10px; color: #333; } .bf-no-values, .bf-info-text { font-style: italic; color: #777; margin-left: 10px; font-size: 12px; } .bf-value-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; padding: 4px; border-radius: 3px; } .bf-value-row:hover { background-color: #f9f9f9; } .bf-value-label { flex-basis: 35%; font-size: 13px; margin-right: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;} .bf-radio-group { display: flex; gap: 8px; pointer-events: auto !important; flex-basis: 65%; justify-content: flex-end; } .bf-radio-label { font-size: 12px; cursor: pointer; display:flex; align-items:center; pointer-events: auto !important; padding: 2px 5px; border-radius:3px; } .bf-radio-label:hover { background-color: #e9e9e9; } .bf-radio-label input[type="radio"] { margin-right: 3px; cursor: pointer; pointer-events: auto !important; z-index: 100001 !important; position: relative; appearance: auto !important; -webkit-appearance: auto !important; -moz-appearance: auto !important; opacity: 1 !important; visibility: visible !important; width: auto !important; height: auto !important; } .bf-input-group { margin-bottom: 10px; } .bf-input-group label { display: block; margin-bottom: 4px; font-weight: bold; font-size: 13px; } .bf-input-group input[type="number"], .bf-input-group textarea { width: 95%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; } .bf-input-group textarea { min-height: 50px; resize: vertical; } #bf-panel-footer { padding: 10px 15px; display: flex; justify-content: space-between; align-items: center; border-top: 1px solid #ccc; background-color: #f9f9f9; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; } #bf-save-settings, #bf-reset-settings { padding: 8px 15px; color: white; border: none; border-radius: 4px; font-size: 14px; } #bf-save-settings { background-color: #5cb85c; } #bf-save-settings:hover { background-color: #4cae4c; } #bf-reset-settings { background-color: #d9534f; } #bf-reset-settings:hover { background-color: #c9302c; } #bf-open-settings-button { position: fixed; bottom: 20px; right: 20px; width: 40px; height: 40px; background-color: #7B1FA2; color: white; border: none; border-radius: 50%; font-size: 20px; font-weight: bold; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 99999 !important; } #bf-open-settings-button:hover { background-color: #6A1B9A; } `); } function logPills(profileCard) { const allTextPillsOnCard = profileCard.querySelectorAll('.pill__title > div.p-3.text-ellipsis.font-weight-medium, .pill__title > div.text-ellipsis.font-weight-medium.p-3'); let cardMatchesAnyNiceCriteria = false; let cardHasAnyNoGo = false; let settingsModifiedByNewValues = false; allTextPillsOnCard.forEach(pillTextElement => { const pillTextOriginal = pillTextElement.textContent.trim(); const pillTextLower = pillTextOriginal.toLowerCase().replace(/\u00a0/g, " ").trim(); if (!pillTextLower) return; let pillTypeKey = null; let imageSrcFound = null; const pillTitleDiv = pillTextElement.parentElement; if (pillTitleDiv && pillTitleDiv.classList.contains('pill__title')) { const commonPillWrapper = pillTextElement.closest('li, .pill, .profile-badge-container, .user-interest, div.p-2, div.profile-v2-interest__badge') || pillTitleDiv.parentElement; if (commonPillWrapper) { const imageElement = commonPillWrapper.querySelector('img.pill__image'); if (imageElement && imageElement.src) { imageSrcFound = imageElement.src.toLowerCase(); for (const key in currentSettings) { if (currentSettings[key] && currentSettings[key].imageSubstring && imageSrcFound.includes(currentSettings[key].imageSubstring.toLowerCase())) { pillTypeKey = key; break; } } } } } if (pillTypeKey) { const category = currentSettings[pillTypeKey]; const stringPillTextLower = String(pillTextLower); if (!category.allKnownValues.map(String).includes(stringPillTextLower)) { category.allKnownValues.push(stringPillTextLower); category.allKnownValues.sort(); settingsModifiedByNewValues = true; console.log(`[Bumble Filter] New value discovered for '${category.label}': "${pillTextOriginal}"`); } let currentPillHighlightColor = HIGHLIGHT_COLOR_INDIFFERENT; let isNice = false; let isNoGo = false; if (category.type === "numberRange") { const heightValue = parseCmValue(stringPillTextLower); if (heightValue !== null) { if (category.noGoSpecificCm && category.noGoSpecificCm.includes(heightValue)) { isNoGo = true; } else { const minOk = category.desiredMinCm === null || heightValue >= category.desiredMinCm; const maxOk = category.desiredMaxCm === null || heightValue <= category.desiredMaxCm; if (minOk && maxOk && (category.desiredMinCm !== null || category.desiredMaxCm !== null)) { isNice = true; } } } } else { isNice = category.desired.map(String).includes(stringPillTextLower); isNoGo = category.noGo.map(String).includes(stringPillTextLower); } if (isNice) { currentPillHighlightColor = HIGHLIGHT_COLOR_NICE; cardMatchesAnyNiceCriteria = true; } else if (isNoGo) { currentPillHighlightColor = HIGHLIGHT_COLOR_NO_GO; cardHasAnyNoGo = true; } pillTextElement.style.backgroundColor = currentPillHighlightColor; pillTextElement.style.borderRadius = '5px'; pillTextElement.style.padding = '2px 4px'; pillTextElement.style.display = 'inline-block'; pillTextElement.style.margin = '1px'; } }); if (settingsModifiedByNewValues) { const panel = document.getElementById('bumble-filter-settings-panel'); if (panel && panel.style.display === 'block') { console.log("[Bumble Filter] New values discovered, refreshing panel content."); buildSettingsPanelContent(panel.querySelector('#bf-panel-content')); } } if (cardHasAnyNoGo) { // profileCard.style.border = `3px solid ${HIGHLIGHT_COLOR_NO_GO}`; } else if (cardMatchesAnyNiceCriteria) { // profileCard.style.border = `3px solid ${HIGHLIGHT_COLOR_NICE}`; } } function processVisibleProfileCards() { let processedSomethingOnThisRun = false; for (const selector of profileCardSelectors) { const cards = document.querySelectorAll(selector); if (cards.length > 0) { cards.forEach(card => { if (!card.dataset.customFilterProcessedV9_0) { // Updated dataset version logPills(card); card.dataset.customFilterProcessedV9_0 = 'true'; processedSomethingOnThisRun = true; } }); if (processedSomethingOnThisRun) break; } } } const observer = new MutationObserver((mutationsList) => { let newNodesAddedInThisBatch = false; for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { newNodesAddedInThisBatch = true; break; } } if (newNodesAddedInThisBatch) { setTimeout(processVisibleProfileCards, 150); } }); function init() { console.log("[Bumble Filter] Script started (v0.9.0 - Radio Buttons & Number Filter)."); loadSettings(); addStyles(); if (!document.getElementById('bf-open-settings-button')) { const openButton = document.createElement('button'); openButton.id = 'bf-open-settings-button'; openButton.textContent = 'B'; openButton.title = 'Bumble Filter Settings'; document.body.appendChild(openButton); openButton.addEventListener('click', () => { let panel = document.getElementById('bumble-filter-settings-panel'); if (!panel) { panel = createSettingsPanelShell(); } if (panel.style.display === 'block') { panel.style.display = 'none'; } else { buildSettingsPanelContent(panel.querySelector('#bf-panel-content')); panel.style.display = 'block'; } }); } let targetNode = document.querySelector('main.page__content') || document.querySelector('#main main') || document.body; if (targetNode) { processVisibleProfileCards(); observer.observe(targetNode, { childList: true, subtree: true }); } else { console.warn("[Bumble Filter] Main target node for observer not found."); const lateObserver = new MutationObserver(() => { targetNode = document.querySelector('main.page__content') || document.querySelector('#main main') || document.body; if (targetNode) { lateObserver.disconnect(); processVisibleProfileCards(); observer.observe(targetNode, { childList: true, subtree: true }); } }); lateObserver.observe(document.body, { childList: true, subtree: true }); } } if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { window.addEventListener('DOMContentLoaded', init); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址