🪝 webhook, HTTPSRV, Einzelne Kommandos per HTML-Link ausführen ohne Passwort

Begonnen von Torxgewinde, 23 Dezember 2024, 22:09:42

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo zusammen,
ich möchte in FHEM einzelne Aktionen über einfache HTTP-Aufrufe (Webhooks) auslösen können.

Will man die Hooks aufrufen und damit den den dahinterliegenden Befehl anstoßen, reicht das Ansurfen von Links mit einem Browser, Apple-Kurzbefehlen, Shelly-URL-Aufrufen etc. Es wird kein CSRF-Token verlangt, dafür sind die Befehle aber auch genau definiert und beschränkt.

Die Befehle und die Schlüsselwörter kann man einfach in dem Hash-Code einfügen. Hinweis: Wenn man mit qq() escaped, gibt es weniger Verwirrung mit den Anführungszeichen wie " oder '.


Mit HTTPSRV kann man dies wie folgt realisieren:

defmod webhook.device HTTPSRV webhook /opt/fhem/www/webhook webhook
attr webhook.device userattr readings
attr webhook.device readings cm,pm
attr webhook.device userReadings state:(cm|pm):.* {\
    my $hash = $defs{$name};;\
    my $cm = ReadingsVal($name, 'cm', '???');; #command\
    my $pm = ReadingsVal($name, 'pm', '???');; #parameter\
    $cm = ($cm =~ s/[^a-zA-Z0-9\?_-]/_/rg);; #sanitize $cm\
    $pm = ($pm =~ s/[^a-zA-Z0-9_-]/_/rg);; #sanitize $pm\
    \
    #List of commands this webhook understands:\
    my %commands = (\
        '???'     => qq({ Log(1, "$name: could not get reading cm") }),\
        'bla'     => qq(setreading $name irgendwas 123),\
        'blubb'   => qq(setreading $name irgendwas 456),\
        'parat_P' => qq(setreading $name irgendwas $pm)\
    );;\
    return 'cmd not found in hash %commands' if not exists $commands{$cm};;\
    \
    #special handling for command with parameter ending with "_P"\
    if($cm =~ /_P$/ and "$pm" eq "---") {\
        #wait for second parameter to be set\
        return;;\
    }\
    readingsBulkUpdate($hash, 'pm', '---') if "$pm" ne "---";;\
    \
    #commands cannot simply be executed from userreading,\
    #so add a 100ms sleep and then execute cmd\
    fhem('sleep 0.1;; '.$commands{$cm});;\
    return "OK: $cm";;\
}

Diese Datei muss an den Ort "/opt/fhem/www/webhook/index.html" und kann angepasst werden:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webhook Commands</title>
    <style>
        /* Basic styling for the page */
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f4f7fc;
        }

        h1 {
            text-align: center;
            color: #333;
        }

        .command-list {
            list-style-type: none;
            padding: 0;
            margin: 20px 0;
        }

        .command-list li {
            background-color: #ffffff;
            border: 1px solid #ddd;
            margin-bottom: 10px;
            padding: 15px;
            border-radius: 5px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .command-list strong {
            font-size: 1.1em;
            color: #333;
        }

        .command-button {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
        }

        .command-button:hover {
            background-color: #45a049;
        }

        .param-input-container {
            display: flex;
            align-items: center;
        }

        .param-input-container label {
            margin-right: 10px;
            font-size: 1em;
        }

        .param-input-container input {
            padding: 8px;
            font-size: 1em;
            margin-right: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }

        .param-input-container button {
            background-color: #008CBA;
            color: white;
            padding: 8px 15px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }

        .param-input-container button:hover {
            background-color: #007B8A;
        }

        .alert {
            margin-top: 20px;
            padding: 10px;
            background-color: #f44336;
            color: white;
            border-radius: 5px;
            display: none;
        }

        .alert.success {
            background-color: #4CAF50;
        }
    </style>
    <script>
        // Function to call webhook for commands ending in '_P' with the parameter from input
        async function callWebhook(command) {
            let paramValue = document.getElementById("paramInput").value;
            let alertBox = document.getElementById("alertBox");

            // If the command ends with '_P', include the parameter
            if (command.endsWith('_P')) {
                if (!paramValue) {
                    alertBox.textContent = "Please enter a parameter value.";
                    alertBox.classList.remove('success');
                    alertBox.classList.add('error');
                    alertBox.style.display = "block";
                    return;
                }
                command += "&pm=" + paramValue;  // Add the pm parameter
            }

            // Construct the full URL
            const url = "/fhem/webhook/index.html?cm=" + command;

            try {
                // Using fetch to make the request without reloading the page
                const response = await fetch(url, {
                    method: 'GET',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });

                // Check if the request was successful
                if (response.ok) {
                    const result = await response.text();  // You can handle the response data here
                    alertBox.textContent = "Webhook called successfully!";
                    alertBox.classList.add('success');
                    alertBox.classList.remove('error');
                    alertBox.style.display = "block";
                } else {
                    alertBox.textContent = "Failed to call webhook.";
                    alertBox.classList.remove('success');
                    alertBox.classList.add('error');
                    alertBox.style.display = "block";
                }
            } catch (error) {
                console.error("Error in fetch request:", error);
                alertBox.textContent = "An error occurred.";
                alertBox.classList.remove('success');
                alertBox.classList.add('error');
                alertBox.style.display = "block";
            }
        }
    </script>
</head>
<body>
    <h1>Webhook Command List</h1>
   
    <div id="alertBox" class="alert"></div>

    <ul class="command-list">
        <li>
            <div>
                <strong>???</strong> - Command: Log(1, "$name: could not get reading cm")
            </div>
            <button class="command-button" onclick="callWebhook('???')">Call Webhook</button>
        </li>
        <li>
            <div>
                <strong>bla</strong> - Command: setreading $name irgendwas 123
            </div>
            <button class="command-button" onclick="callWebhook('bla')">Call Webhook</button>
        </li>
        <li>
            <div>
                <strong>blubb</strong> - Command: setreading $name irgendwas 456
            </div>
            <button class="command-button" onclick="callWebhook('blubb')">Call Webhook</button>
        </li>
        <li>
            <div>
                <strong>parat_P</strong> - Command: setreading $name irgendwas $pm
            </div>
            <div class="param-input-container">
                <label for="paramInput">Parameter:</label>
                <input type="text" id="paramInput" name="paramInput" placeholder="Enter parameter value">
                <button class="command-button" onclick="callWebhook('parat_P')">Call Webhook</button>
            </div>
        </li>
    </ul>
</body>
</html>

So sieht es dann aus:
Du darfst diesen Dateianhang nicht ansehen.