您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
one button click -> simplify propertyguru listing info for easily copy / paste
当前为
// ==UserScript== // @name PropertyguruAssist // @namespace http://tampermonkey.net/ // @version 0.99 // @description one button click -> simplify propertyguru listing info for easily copy / paste // @author EnginePlus // @match https://*.propertyguru.com.sg/listing/* // @match https://*.commercialguru.com.sg/listing/* // @match https://*.propertyguru.com.my/property-listing/* // @grant none // @resource customCSS https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css // @require https://greasyfork.org/scripts/27254-clipboard-js/code/clipboardjs.js?version=174357 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js // ==/UserScript== (function () { 'use strict'; function getValueByLabel(items, label) { if (!Array.isArray(items)) return 'N.A.'; let item = items.find(item => item.label === label || item.icon === label); return item ? item.value : 'N.A.'; } function safe(fn, fallback = 'N.A.') { try { const val = fn(); return (val !== undefined && val !== null && val !== '') ? val : fallback; } catch { return fallback; } } function isLoginRequired() { const buttons = Array.from(document.querySelectorAll('div.btn-content')); return buttons.some(el => el.textContent.trim().toLowerCase() === 'login'); } function extractData() { const jsonData = safe(() => JSON.parse(document.getElementById('__NEXT_DATA__').textContent), {}); const root = jsonData?.props?.pageProps?.pageData?.data; const metatableItems = safe(() => root.detailsData.metatable.items, []); return { url: window.location.href, propertyName: safe(() => root.listingData.localizedTitle), tenureType: getValueByLabel(metatableItems, 'calendar-days-o'), topYear: getValueByLabel(metatableItems, 'document-with-lines-o'), totalUnits: getValueByLabel(metatableItems, 'block-o'), bedNum: safe(() => root.listingData.bedrooms), bathNum: safe(() => root.listingData.bathrooms), floorSize: safe(() => root.listingData.floorArea), price: safe(() => root.propertyOverviewData.propertyInfo.price.amount), agentName: safe(() => root.contactAgentData.contactAgentCard.agentInfoProps.agent.name), phoneNumber: safe(() => root.listingData.agent.mobile || root.contactAgentData.contactAgentCard.contactActions?.[0]?.phoneNumber) }; } function copyToClipboard(data, checkboxes, button) { const get = key => checkboxes[key]?.checked ? (data[key] || 'N.A.') : ''; const mainInfo = get('propertyName') + ' [' + get('tenureType') + ' / ' + get('topYear') + ' / ' + get('totalUnits') + ']' + ', ' + get('bedNum') + ' Bed, ' + get('bathNum') + ' Bath, ' + get('floorSize') + ' sqft, ' + get('price'); const agentInfo = get('agentName') + ' ' + get('phoneNumber'); const clipboardText = data.url + '\t' + mainInfo + '\t' + agentInfo; navigator.clipboard.writeText(clipboardText).then(() => { if (button) { button.textContent = '已复制!'; setTimeout(() => (button.textContent = '复制到剪贴板'), 2000); } }); } // ✅ 构造 WhatsApp 链接函数 function formatPhoneNumber(raw) { const phone = raw.replace(/\D/g, ''); return phone.startsWith('65') ? phone : '65' + phone; } function buildWhatsAppLink(agentName, listingUrl, rawPhone) { const phone = formatPhoneNumber(rawPhone); const message = `Hi ${agentName}, My client would like to learn more about your listing: ${listingUrl}`; const encodedText = encodeURIComponent(message); return `https://api.whatsapp.com/send?phone=${phone}&text=${encodedText}`; } function createPanel(data) { const groups = [ { key: 'infoBlock', label: '房源基本信息', fields: ['propertyName', 'tenureType', 'topYear', 'totalUnits', 'bedNum', 'bathNum', 'floorSize', 'price'] }, { key: 'agentBlock', label: '联系人信息', fields: ['agentName', 'phoneNumber'] } ]; const panel = document.createElement('div'); panel.style.position = 'fixed'; panel.style.bottom = '20px'; panel.style.right = '20px'; panel.style.background = '#fdfdfd'; panel.style.border = '1px solid #ccc'; panel.style.borderRadius = '8px'; panel.style.boxShadow = '2px 2px 12px rgba(0,0,0,0.2)'; panel.style.fontSize = '14px'; panel.style.minWidth = '320px'; panel.style.zIndex = 9999; panel.style.cursor = 'move'; panel.setAttribute("id", "floatingPanel"); const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.padding = '8px'; header.style.backgroundColor = '#4A90E2'; header.style.color = '#fff'; header.style.borderTopLeftRadius = '8px'; header.style.borderTopRightRadius = '8px'; header.style.cursor = 'move'; const titleText = document.createElement('span'); titleText.textContent = 'v0.99 小助手提取信息'; titleText.style.fontWeight = 'bold'; header.appendChild(titleText); if (isLoginRequired()) { const warn = document.createElement('span'); warn.textContent = '未登录 可能不显示联络号码'; warn.style.color = 'red'; warn.style.fontWeight = 'normal'; warn.style.fontSize = '12px'; warn.style.marginLeft = '10px'; header.appendChild(warn); } panel.appendChild(header); const content = document.createElement('div'); content.style.padding = '10px'; content.style.backgroundColor = '#ffffff'; const checkboxes = {}; groups.forEach(group => { const title = document.createElement('div'); title.textContent = group.label; title.style.fontWeight = 'bold'; title.style.marginTop = '10px'; content.appendChild(title); group.fields.forEach(key => { const wrapper = document.createElement('div'); wrapper.style.marginBottom = '4px'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = true; checkbox.id = key; checkboxes[key] = checkbox; const label = document.createElement('label'); label.htmlFor = key; label.textContent = ' ' + key + ': '; const valueSpan = document.createElement('span'); valueSpan.style.color = '#333'; valueSpan.textContent = data[key] || 'N.A.'; wrapper.appendChild(checkbox); wrapper.appendChild(label); wrapper.appendChild(valueSpan); content.appendChild(wrapper); }); }); const button = document.createElement('button'); button.textContent = '复制到剪贴板'; button.style.padding = '5px 10px'; button.style.fontSize = '13px'; button.style.cursor = 'pointer'; button.onclick = () => copyToClipboard(data, checkboxes, button); const buttonGroup = document.createElement('div'); buttonGroup.style.marginTop = '10px'; buttonGroup.appendChild(button); // ✅ 增加带文本的 WhatsApp 联系按钮 if (data.phoneNumber && data.phoneNumber !== 'N.A.') { const whatsappBtn = document.createElement('button'); whatsappBtn.textContent = 'WhatsApp联系中介'; whatsappBtn.style.marginLeft = '10px'; whatsappBtn.style.padding = '5px 10px'; whatsappBtn.style.fontSize = '13px'; whatsappBtn.style.cursor = 'pointer'; whatsappBtn.onclick = () => { const url = buildWhatsAppLink(data.agentName, data.url, data.phoneNumber); window.open(url, '_blank'); }; buttonGroup.appendChild(whatsappBtn); } content.appendChild(buttonGroup); panel.appendChild(content); document.body.appendChild(panel); makeDraggable(panel, header); copyToClipboard(data, checkboxes, button); } function makeDraggable(panel, handle) { let isDragging = false; let offsetX, offsetY; handle.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - panel.getBoundingClientRect().left; offsetY = e.clientY - panel.getBoundingClientRect().top; panel.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { if (isDragging) { panel.style.left = `${e.clientX - offsetX}px`; panel.style.top = `${e.clientY - offsetY}px`; panel.style.bottom = 'auto'; panel.style.right = 'auto'; } }); document.addEventListener('mouseup', () => { isDragging = false; panel.style.cursor = 'move'; }); } if (document.readyState === 'loading') { document.addEventListener("DOMContentLoaded", () => { const data = extractData(); createPanel(data); }); } else { const data = extractData(); createPanel(data); } })();