您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在极客页面中,点击设备列表,调用API获取设备和规则列表,并生成设备规则映射并显示在当前页面上
当前为
// ==UserScript== // @name 米家极客版油猴插件 // @namespace http://tampermonkey.net/ // @version v0.8.4 // @description 在极客页面中,点击设备列表,调用API获取设备和规则列表,并生成设备规则映射并显示在当前页面上 // @author 王丰,sk163 // @license MIT // @match http://*/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (async () => { const callAPI = (api, params) => { return new Promise(res => editor.gateway.callAPI(api, params, res)); }; let intervalId; let selectCardIds = ''; let defaultColor='#43ad7f7f' let defaultWindowWidth=800; let defaultWindowHeight=800; let enableEnhancedDisplayLog=GM_getValue("enableEnhancedDisplayLog"); let backgroundColor = GM_getValue("backgroundColor") ; let windowWidth = GM_getValue("windowWidth"); let windowHeight = GM_getValue("windowHeight"); if (enableEnhancedDisplayLog === undefined || enableEnhancedDisplayLog === null || enableEnhancedDisplayLog === "") { enableEnhancedDisplayLog = true; } if (backgroundColor === undefined || backgroundColor === null || backgroundColor === "") { backgroundColor = defaultColor; } if (windowWidth === undefined || windowWidth === null || windowWidth === "") { windowWidth = defaultWindowWidth; } else { windowWidth = parseInt(windowWidth, 10); if (isNaN(windowWidth) || windowWidth <= 0) { windowWidth = defaultWindowWidth; } } if (windowHeight === undefined || windowHeight === null || windowHeight === "") { windowHeight = defaultWindowHeight; } else { windowHeight = parseInt(windowHeight, 10); if (isNaN(windowHeight) || windowHeight <= 0) { windowHeight = defaultWindowHeight; } } const executeScript = async () => { if (document.getElementById('device-rule-map')) { return; } if (typeof editor === 'undefined' || typeof editor.gateway === 'undefined' || typeof editor.gateway.callAPI === 'undefined') { console.error('editor.gateway.callAPI 方法未定义。请确保在正确的环境中运行此脚本。'); return; } try { const devListResponse = await callAPI('getDevList'); const devList = devListResponse.devList; const roomNames = Array.from(new Set(Object.values(devList).map(device => device.roomName))); const ruleList = await callAPI('getGraphList'); let devRuleMap = {}; for (const rule of ruleList) { const content = await callAPI('getGraph', {id: rule.id}); const dids = new Set(content.nodes.map(n => n.props?.did).filter(did => did !== undefined)); const cards = new Set(content.nodes.map(n => { return (n.props && n.cfg) ? {did: n.props.did, oriId: n.cfg.oriId} : undefined; }).filter(card => card !== undefined)); dids.forEach(did => { devRuleMap[did] = devRuleMap[did] ?? []; const cardIds = Array.from(cards) .filter(card => card.did === did) .map(card => card.oriId).join(','); const tempDevRule = { ruleId: rule.id, cardIds: cardIds, totalCardNum: cards.size }; devRuleMap[did].push(tempDevRule); }); } const result = Object.fromEntries( Object.entries(devRuleMap).map(([k, v]) => [ k, { device: { name: devList[k]?.name ?? `did: ${k}`, roomName: devList[k]?.roomName ?? `未知` }, rules: v.map(r => { const rule = ruleList.find(rr => rr.id === r.ruleId); return { id: r.ruleId, cardIds: r.cardIds, totalCardNum: r.totalCardNum, name: rule ? rule.userData.name : 'Unknown' // 添加对未找到规则的检查 }; }) } ]) ); const container = document.createElement('div'); container.id = 'device-rule-map'; container.style.position = 'fixed'; container.style.top = '10px'; container.style.right = '10px'; container.style.width = windowWidth+'px'; container.style.height = windowHeight+'px'; container.style.overflowY = 'scroll'; container.style.backgroundColor = 'white'; container.style.border = '1px solid #ccc'; container.style.paddingTop = '50px'; container.style.zIndex = 10000; container.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)'; const topBar = document.createElement('div'); topBar.style.position = 'fixed'; topBar.style.top = '0'; topBar.style.right = '10px'; topBar.style.width = windowWidth+'px'; topBar.style.height = '50px'; topBar.style.backgroundColor = 'white'; topBar.style.zIndex = 10001; topBar.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)'; topBar.style.display = 'flex'; topBar.style.justifyContent = 'space-between'; topBar.style.alignItems = 'center'; topBar.style.padding = '0 10px'; const title = document.createElement('h1'); title.style.margin = '0'; title.textContent = '设备规则映射结果'; const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '10px'; const collapseButton = document.createElement('button'); collapseButton.textContent = '折叠'; collapseButton.onclick = () => { if (container.style.height === windowHeight+'px') { topBar.style.width = '350px'; container.style.height = '0px'; container.style.width = '0px'; collapseButton.textContent = '展开'; } else { topBar.style.width = windowWidth+'px'; container.style.width = windowWidth+'px'; container.style.height = windowHeight+'px'; collapseButton.textContent = '折叠'; } }; const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.onclick = () => document.body.removeChild(container); buttonContainer.appendChild(collapseButton); buttonContainer.appendChild(closeButton); topBar.appendChild(title); topBar.appendChild(buttonContainer); const table = document.createElement('table'); table.border = '1'; table.cellSpacing = '0'; table.cellPadding = '5'; table.style.width = '100%'; const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); const roomHeader = document.createElement('th'); const deviceHeader = document.createElement('th'); const ruleHeader = document.createElement('th'); let roomSortOrder = 'asc'; let deviceSortOrder = 'asc'; let ruleSortOrder = 'asc'; const updateSortMarkers = () => { roomHeader.innerHTML = `房间 ${roomSortOrder === 'asc' ? '⬆️' : '⬇️'}`; deviceHeader.innerHTML = `设备 ${deviceSortOrder === 'asc' ? '⬆️' : '⬇️'}`; ruleHeader.innerHTML = `规则 ${ruleSortOrder === 'asc' ? '⬆️' : '⬇️'}`; }; roomHeader.textContent = '房间'; roomHeader.style.textWrap= 'nowrap'; deviceHeader.textContent = '设备'; deviceHeader.style.textWrap = 'nowrap'; ruleHeader.textContent = '规则'; roomHeader.onclick = () => { roomSortOrder = roomSortOrder === 'asc' ? 'desc' : 'asc'; sortTable(0, roomSortOrder); updateSortMarkers(); }; deviceHeader.onclick = () => { deviceSortOrder = deviceSortOrder === 'asc' ? 'desc' : 'asc'; sortTable(1, deviceSortOrder); updateSortMarkers(); }; ruleHeader.onclick = () => { ruleSortOrder = ruleSortOrder === 'asc' ? 'desc' : 'asc'; sortTable(2, ruleSortOrder); updateSortMarkers(); }; headerRow.appendChild(roomHeader); headerRow.appendChild(deviceHeader); headerRow.appendChild(ruleHeader); thead.appendChild(headerRow); table.appendChild(thead); const roomFilterSelect = document.createElement('select'); roomFilterSelect.style.marginBottom = '10px'; roomFilterSelect.style.height = '28px'; roomFilterSelect.innerHTML = `<option value="">所有房间</option>` + roomNames.map(room => `<option value="${room}">${room}</option>`).join(''); roomFilterSelect.onchange = () => { filterTable(roomFilterSelect.value,deviceFilterInput.value, ruleFilterInput.value); }; container.appendChild(roomFilterSelect); const deviceFilterInput = document.createElement('input'); deviceFilterInput.type = 'text'; deviceFilterInput.placeholder = '设备筛选'; deviceFilterInput.style.width = '100px'; deviceFilterInput.style.marginBottom = '10px'; deviceFilterInput.style.marginLeft = '10px'; deviceFilterInput.style.height = '28px'; deviceFilterInput.oninput = () => { filterTable(roomFilterSelect.value,deviceFilterInput.value, ruleFilterInput.value); }; container.appendChild(deviceFilterInput); const ruleFilterInput = document.createElement('input'); ruleFilterInput.type = 'text'; ruleFilterInput.placeholder = '规则筛选'; ruleFilterInput.style.width = '100px'; ruleFilterInput.style.marginBottom = '10px'; ruleFilterInput.style.marginLeft = '10px'; ruleFilterInput.style.height = '28px'; ruleFilterInput.oninput = () => { filterTable(roomFilterSelect.value,deviceFilterInput.value, ruleFilterInput.value); }; container.appendChild(ruleFilterInput); const widthInput = document.createElement('input'); widthInput.type = 'text'; widthInput.placeholder = windowWidth+'px'; widthInput.style.width = '60px'; widthInput.style.marginBottom = '10px'; widthInput.style.marginLeft = '10px'; widthInput.style.height = '28px'; widthInput.oninput = () => { windowWidth=widthInput.value; GM_setValue("windowWidth", windowWidth); container.style.width = windowWidth + 'px'; topBar.style.width = windowWidth + 'px'; }; const spanW = document.createElement('span'); spanW.textContent = '宽度:'; spanW.style.marginLeft = '10px'; container.appendChild(spanW); container.appendChild(widthInput); const heightInput = document.createElement('input'); heightInput.type = 'text'; heightInput.placeholder = windowHeight+'px'; heightInput.style.width = '60px'; heightInput.style.marginBottom = '10px'; heightInput.style.marginLeft = '10px'; heightInput.style.height = '28px'; heightInput.oninput = () => { windowHeight=heightInput.value; GM_setValue("windowHeight", windowHeight); container.style.height = windowHeight + 'px'; }; const spanH = document.createElement('span'); spanH.textContent = '高度:'; spanH.style.marginLeft = '10px'; container.appendChild(spanH); container.appendChild(heightInput); const colorInput = document.createElement('input'); colorInput.type = 'text'; colorInput.placeholder=defaultColor; colorInput.style.width = '80px'; colorInput.style.marginBottom = '10px'; colorInput.style.marginLeft = '10px'; colorInput.style.height = '28px'; colorInput.oninput = () => { backgroundColor = colorInput.value; GM_setValue("backgroundColor", backgroundColor); }; const spanC = document.createElement('span'); spanC.textContent = '卡片颜色:'; spanC.style.marginLeft = '10px'; container.appendChild(spanC); container.appendChild(colorInput); const label = document.createElement('label'); label.htmlFor = 'highlightLogCheck'; label.appendChild(document.createTextNode('日志高亮')); label.style.marginBottom = '10px'; label.style.marginLeft = '10px'; container.appendChild(label); const highlightLogCheck = document.createElement('input'); highlightLogCheck.type = 'checkbox'; highlightLogCheck.id = 'highlightLogCheck'; highlightLogCheck.checked=enableEnhancedDisplayLog; highlightLogCheck.style.marginLeft = '5px'; highlightLogCheck.onchange=function() { enableEnhancedDisplayLog = highlightLogCheck.checked; GM_setValue("enableEnhancedDisplayLog", enableEnhancedDisplayLog); enhancedDisplayLog(); }; container.appendChild(highlightLogCheck); const tbody = document.createElement('tbody'); Object.entries(result).forEach(([did, data]) => { const device=data.device; const rules=data.rules; const row = document.createElement('tr'); const roomCell = document.createElement('td'); roomCell.textContent = device.roomName; roomCell.style.textWrap= 'nowrap'; const deviceCell = document.createElement('td'); deviceCell.textContent = device.name; deviceCell.style.textWrap= 'nowrap'; const ruleCell = document.createElement('td'); const host = window.location.host; rules.forEach(rule => { const link = document.createElement('a'); link.href = `http://${host}/#/graph/${rule.id}`; link.target = '_self'; link.textContent = rule.name + "[" + rule.cardIds.split(',').length + "/" + rule.totalCardNum + "]"; link.onclick = () => { window.location.hash = '#/'; selectCardIds = rule.cardIds; }; ruleCell.appendChild(link); ruleCell.appendChild(document.createTextNode(', ')); }); ruleCell.removeChild(ruleCell.lastChild); row.appendChild(roomCell); row.appendChild(deviceCell); row.appendChild(ruleCell); tbody.appendChild(row); }); table.appendChild(tbody); container.appendChild(topBar); container.appendChild(table); document.body.appendChild(container); function sortTable(columnIndex, sortOrder) { const rows = Array.from(tbody.rows); const sortedRows = rows.sort((a, b) => { const aText = a.cells[columnIndex].textContent; const bText = b.cells[columnIndex].textContent; if (sortOrder === 'asc') { return aText.localeCompare(bText); } else { return bText.localeCompare(aText); } }); tbody.innerHTML = ''; sortedRows.forEach(row => tbody.appendChild(row)); } updateSortMarkers(); function filterTable(roomName,deviceKeyword, ruleKeyword) { const rows = Array.from(tbody.rows); rows.forEach(row => { const roomText = row.cells[0].textContent; const deviceText = row.cells[1].textContent.toLowerCase(); const ruleText = row.cells[2].textContent.toLowerCase(); if ((roomName === '' || roomText === roomName) && deviceText.includes(deviceKeyword.toLowerCase()) && ruleText.includes(ruleKeyword.toLowerCase())) { row.style.display = ''; } else { row.style.display = 'none'; } }); } } catch (error) { console.error('调用 API 时出错:', error); } }; const selectDevices = async () => { await sleep(1000); const cardIds = selectCardIds.split(','); for (const cardId of cardIds) { if (cardId.trim() !== '') { let targetElement = document.querySelector("#" + cardId.trim() + " > div > div"); if (targetElement) { targetElement.style.backgroundColor = backgroundColor === '' ? defaultColor : backgroundColor; } } } selectCardIds = ''; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } }; function enhancedDisplayLog() { if (!enableEnhancedDisplayLog) { if (intervalId) { clearInterval(intervalId); } } else { intervalId = setInterval(() => { var element = document.querySelector('.panel-log-card-blink'); if (element && element.style.outline !== "red solid 20px") { element.style.outline = "red solid 10px"; } let animateElement = document.querySelector('animate'); if (animateElement && animateElement.getAttribute('stroke-width') != '10') { console let pathElement = animateElement.parentElement; pathElement.setAttribute('stroke-width', '10'); if (pathElement) { let gElement = pathElement.parentElement; gElement.setAttribute('stroke', 'red'); } } }, 500); } } function isMiJiaJiKePage() { return document.title === "米家自动化极客版" && !document.querySelector('.pin-form'); } if(isMiJiaJiKePage()){ enhancedDisplayLog(); executeScript(); } window.addEventListener('hashchange', () => { if (isMiJiaJiKePage()) { enhancedDisplayLog(); executeScript(); if (window.location.hash.match(/^#\/graph\/.*/g)) { selectDevices(); } } }); })();