米家极客版油猴插件

在极客页面中,点击设备列表,调用API获取设备和规则列表,并生成设备规则映射并显示在当前页面上

目前為 2024-05-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         米家极客版油猴插件
// @namespace    http://tampermonkey.net/
// @version      v0.6
// @description  在极客页面中,点击设备列表,调用API获取设备和规则列表,并生成设备规则映射并显示在当前页面上
// @author       王丰,sk163
// @license      MIT
// @match        http://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==
(async () => {
    const callAPI = (api, params) => {
        return new Promise(res => editor.gateway.callAPI(api, params, res));
    };
    let selectCardIds = '';
    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(',');
                    devRuleMap[d].push(rule.id + "#" + cardIds);
                });
            }

            const result = Object.fromEntries(Object.entries(devRuleMap).map(([k, v]) => [
                devList[k]?.name ?? `did: ${k}`,
                v.map(r => {
                    const ruleId = r.split('#')[0];
                    const cardIds = r.split('#')[1];
                    return ({
                        id: ruleId,
                        cardIds: cardIds,
                        name: ruleList.find(rr => rr.id === 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 = '800px';
            container.style.height = '600px';
            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 = '800px';
            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 === '600px') {
                    container.style.height = '50px';
                    collapseButton.textContent = '展开';
                } else {
                    container.style.height = '600px';
                    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 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;
                    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();

        } 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 = '#F08080';
                }
            }
        }
        selectCardIds = '';

        function sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }
    };

    function isMiJiaJiKePage(){
        return document.title==="米家自动化极客版";

    }

    // 检查初始的 hash 值
    if (isMiJiaJiKePage() && window.location.hash === '#/device') {
        executeScript();
    }

    // 监听 hash 值变化
    window.addEventListener('hashchange', () => {
        if (isMiJiaJiKePage() && window.location.hash === '#/device') {
            executeScript();
        }
        if (isMiJiaJiKePage() && window.location.hash.match(/^#\/graph\/.*/g) ) {
            selectDevices();
        }
    });
})();