Greasy Fork is available in English.
在极客页面中,点击设备列表,调用API获取设备和规则列表,并生成设备规则映射并显示在当前页面上
当前为
// ==UserScript==
// @name 米家极客版油猴插件
// @namespace http://tampermonkey.net/
// @version v0.8
// @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 selectCardIds = '';
let defaultColor='#43ad7f7f'
let backgroundColor = GM_getValue("backgroundColor") ?? '';
let windowWidth = GM_getValue("windowWidth") ?? 800;
let windowHeight= GM_getValue("windowHeight") ?? 600;
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 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(d => d !== undefined));
const cards = new Set(content.nodes.map(n => {
return (n.props && n.cfg) ? {did: n.props.did, oriId: n.cfg.oriId} : undefined;
}).filter(d => d !== undefined));
dids.forEach(d => {
devRuleMap[d] = devRuleMap[d] ?? [];
const cardIds = Array.from(cards)
.filter(card => card.did === d)
.map(card => card.oriId).join(',');
const tempDevRule = {
ruleId: rule.id,
cardIds: cardIds,
totalCardNum: cards.size
};
devRuleMap[d].push(tempDevRule);
});
}
const result = Object.fromEntries(Object.entries(devRuleMap).map(([k, v]) => [
devList[k]?.name ?? `did: ${k}`,
v.map(r => {
return ({
id: r.ruleId,
cardIds: r.cardIds,
totalCardNum: r.totalCardNum,
name: ruleList.find(rr => rr.id === r.ruleId).userData.name
});
})
]));
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 = '50px';
container.style.width = '50px';
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 deviceHeader = document.createElement('th');
const ruleHeader = document.createElement('th');
let deviceSortOrder = 'asc';
let ruleSortOrder = 'asc';
const updateSortMarkers = () => {
deviceHeader.innerHTML = `设备 ${deviceSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
ruleHeader.innerHTML = `规则 ${ruleSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
};
deviceHeader.textContent = '设备';
ruleHeader.textContent = '规则';
deviceHeader.onclick = () => {
deviceSortOrder = deviceSortOrder === 'asc' ? 'desc' : 'asc';
sortTable(0, deviceSortOrder);
updateSortMarkers();
};
ruleHeader.onclick = () => {
ruleSortOrder = ruleSortOrder === 'asc' ? 'desc' : 'asc';
sortTable(1, ruleSortOrder);
updateSortMarkers();
};
headerRow.appendChild(deviceHeader);
headerRow.appendChild(ruleHeader);
thead.appendChild(headerRow);
table.appendChild(thead);
const deviceFilterInput = document.createElement('input');
deviceFilterInput.type = 'text';
deviceFilterInput.placeholder = '设备筛选';
deviceFilterInput.style.marginBottom = '10px';
deviceFilterInput.oninput = () => {
filterTable(deviceFilterInput.value, ruleFilterInput.value);
};
container.appendChild(deviceFilterInput);
const ruleFilterInput = document.createElement('input');
ruleFilterInput.type = 'text';
ruleFilterInput.placeholder = '规则筛选';
ruleFilterInput.style.marginBottom = '10px';
ruleFilterInput.style.marginLeft = '10px';
ruleFilterInput.oninput = () => {
filterTable(deviceFilterInput.value, ruleFilterInput.value);
};
container.appendChild(ruleFilterInput);
const widthInput = document.createElement('input');
widthInput.type = 'text';
widthInput.placeholder = windowWidth+'px';
widthInput.style.width = '80px';
widthInput.style.marginBottom = '10px';
widthInput.style.marginLeft = '10px';
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 = '80px';
heightInput.style.marginBottom = '10px';
heightInput.style.marginLeft = '10px';
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.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 tbody = document.createElement('tbody');
Object.entries(result).forEach(([device, rules]) => {
const row = document.createElement('tr');
const deviceCell = document.createElement('td');
deviceCell.textContent = device;
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(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(deviceKeyword, ruleKeyword) {
const rows = Array.from(tbody.rows);
rows.forEach(row => {
const deviceText = row.cells[0].textContent.toLowerCase();
const ruleText = row.cells[1].textContent.toLowerCase();
if (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 isMiJiaJiKePage() {
return document.title === "米家自动化极客版";
}
if (isMiJiaJiKePage() && window.location.hash === '#/device') {
executeScript();
}
window.addEventListener('hashchange', () => {
if (isMiJiaJiKePage() && window.location.hash === '#/device') {
executeScript();
}
if (isMiJiaJiKePage() && window.location.hash.match(/^#\/graph\/.*/g)) {
selectDevices();
}
});
})();