Bot für Synology Chat

Status
Für weitere Antworten geschlossen.

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Da es hier noch nicht viele Threads zum Thema Synology Chat und Chat Integration gibt, dachte ich mir, ich teile hier mal meine ersten Schritte in Richtung eines “ChatBots”.

Nachdem ich dieses Support-Dokument gefunden habe, war ich sofort Feuer und Flamme irgendetwas mit dieser Schnittstelle zu machen. Der Gedanke an einen eigenen, self-hosted Bot à la Google Assistent, mit dem ich mich in einer Chat-Umgebung austauschen kann, hat mich einfach begeistert.

Um mich mit php vertraut zu machen habe ich ein Skript für Incoming Webhooks zusammengeklöppelt, womit ein php-fähiges Gerät also in Eigeninitiative Nachrichten in Chat postet und was von der Shell oder in Skripten ausgeführt werden kann. Dank geht dabei an heavygale und diesen Beitrag, von dem ich die Funktion send_chat() geklaut habe :D Ohne die hätte ich gar nicht gewusst, wie ich anzufangen habe.

Hier ist der Code:
Rich (BBCode):
/*
Basic message-script for Synology Chat. Written for PHP 5.4 and above.
Usage:
php chatbot.php [Bot] [Message Type] [Content]
Examples:
/bin/php chatbot.php TestBot Message1
/bin/php chatbot.php SynoBot Text "Testnachricht"
/bin/php chatbot.php TestBot URL "http://example.org"


"Message Type" can be either one of the pre-definded messages below or one of the following: "Text", "URL" or "File".
For those three the [Content] Parameter as string is needed, in form of plain text for "Text" or an URL for "URL" and "File"

Pre-defined Messages:
	[Message1]
	[Message2]

	[Bootup]
	[Shutdown]

	[KlingelEvent]
*/

// Device name for message integration
$source = "Syno1"; //Syno2, RaspPi, etc.

// Webhook URLs for different Bots
$Bots = [
	TestBot => "https://example.org/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=1&token=XXXXXXXXX",
	SynoBot => "https://example.org/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=1&token=XXXXXXXXX",
	KlingelBot => "https://example.org/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=1&token=XXXXXXXXX",
];

// Chat users to address, so they get Push Notifications. @channel equals every member in a channel
$Users = [
	"@channel",
	"@User1",
	"@User2",
];

function send_chat($webhook, $message) { 
    $opts = ['http' => 
				[ 
     			   'method'  => 'POST', 
      			 'content' => $message, 
				],
				'ssl' =>
				[
					'verify_peer' => true //Toggle SSL-verification
				],
	]; 
    $context = stream_context_create($opts); 
    return file_get_contents($webhook, false, $context); 
}

function set_payload($Text) {
	$a = 'payload={"text": "' . $Text . '"}';
	return $a;
}

function set_payload_long($Type, $Text) {
	switch ($Type) {
		case "Text":
			$b = set_payload($Text);
			break;
		case "URL":
			$b = 'Link <"' . $Text . '">';
			$b = set_payload($b);
			break;
		case "File":
			$b = 'payload={"text": "File Link", "file_url": "' . $Text . '"}';
			break;
		default:
			$b = error_message($Type, true);
			echo 'Second Argument invalid: [Text], or [File] allowed. Usage: php chat...weisen habe, werde ich es hier posten.

Grüße
 

Thonav

Benutzer
Sehr erfahren
Mitglied seit
16. Feb 2014
Beiträge
7.874
Punkte für Reaktionen
1.496
Punkte
274
Cool. Werde Deinen weiteren Tätigkeiten folgen. Kann nur leider nichts produktives beisteuern. ;)
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
So, ich melde mich zurück mit einem ersten Entwurf für Outgoing Webhooks.
Hiermit will ich zunächst einfach mal aufzeigen, wie der POST Request der Syno aussieht, man ihn verarbeiten und eine Antwort zurücksenden kann, die dann in Chat angezeigt wird.
Hier ein Beispiel für die Daten, die Synology Chat an das Skript übermittelt, wenn User "admin" den Text "Webhook Test" in einen Channel schreibt, welcher "Webhook" als trigger definiert hat:
Rich (BBCode):
token=XXXXXXXXXXXX&
channel_id=5&
channel_id=5&
channel_type=1&
channel_name=Test&
user_id=3&
username=admin&
post_id=21474836505&
timestamp=1510167724994&
text=Webhook%20Test&
trigger_word=Webhook

Im Skript wird dieser String dann in arrays gesplittet, um auf die einzelnen Werte zugreifen zu können. Das trigger_word bestimmt dann, was im Skript ausgeführt wird.
Als ein Beispiel ist eine Funktion im Skript, die verschiedene Geräte pingt und deren Status als Antwort sendet.

Hier der Code(für Verbesserungen und Anregungen bin ich immer offen :D):
Rich (BBCode):
/* PHP-Script for Outgoing Webhooks from Synology Chat App
Usage:
- Setup an Outgoing Webhook in Synology Chat. Documentation can be found here: https://www.synology.com/en-global/knowledgebase/DSM/help/Chat/chat_integration
	- Set the URL to this script, for example: https://my-address.com/webhook-response.php
	- Add the Token in $Bots array below.
- Set $Users array to your preference

Example of POST data fields for Outgoing Webhook:
token=XXXXXXXXXXXX&channel_id=5&channel_id=5&channel_type=1&channel_name=Test&user_id=3&username=admin&post_id=21474836505&timestamp=1510167724994&text=Webhook%20Test&trigger_word=Webhook
*/

// Chat users to address, so they get Push Notifications. @channel equals every member in a channel
$Users = [
	"@channel",
	"@User1",
	"@User2",
];

//Token Verification
function verify($token) {
	$Bots = [
		Test => "XXXXXXXXXXXX",
		OutgoingBot => "YYYYYYYYYYYY"
	];
	foreach ($Bots as $key => $value) {
		if ($token == $value) {
			// echo "Token gefunden";
			return;
		}
	}
	exit("unable to verify Chat token");
}

function get_temperature() {
	//$a = exec("/bin/sh /opt/temp.sh", $output, $temp);
	$temp = 5; //Placeholder for testing
	return $temp;
}

//Change /sbin/ping to the location of ping, depending on the OS
function netstat($device) {
	$devPool = [
		Test => ["127.0.0.1" => "offline"],
		PC1 => ["192.168.178.20" => "offline"],
		PC2 => ["192.168.178.21" => "offline"],
		PC3 => ["192.168.178.22" => "offline"]
	];
	if ($device == "all") {
		$a = "Devices:\n";
		foreach ($devPool as $key => $value) {
			foreach ($value as $ip => $status) {
				$pingresult = exec("/bin/ping -c 3 $ip", $outcome, $status);
				if ($status == 0) {$devPool[$key][$ip] = "online";}
				$a = $a . $key . ": " . $devPool[$key][$ip] . "\n";
			}
		}
		return $a;
	} else {
		foreach($devPool as $key => $value) {
			if ($device == $key) {
				foreach ($value as $ip => $status) {
					$pingresult = exec("/bin/ping -c 3 $ip", $outcome, $status);
					if ($status == 0) {$devPool[$key][$ip] = "online";}
					$a = $device . ": " . $devPool[$key][$ip];
					return $a;
				}
			}
		}
	}
	
}

$input = file_get_contents('php://input');
//input example for testing
//$input = 'token=XXXXXXXXXXXX&channel_id=5&channel_id=5&channel_type=1&channel_name=Test&user_id=3&username=admin&post_id=21474836505&timestamp=1510167724994&text=Webhook%20Test&trigger_word=Webhook';
parse_str($input, $data); //Creates array with POST data fields from the request
$text = explode(" ", $data["text"]); //array with every word in "text" data field

verify($data["token"]);

switch ($data["trigger_word"]) {
	case "Webhook":
		$output = $Users[1] . " Webhook-Test erfolgreich!";
		break;
	case "Temperatur":
		$t = get_temperature();
		$output = "Innentemperatur bei " . $t . "C";
		break;
	case "Netstat":
		if (isset($text[1])) {$output = netstat($text[1]);}
		break;
}

$context = [ 'text' => $output ];
header('Content-Type: application/json');
echo json_encode($context);


Zwei Punkte möchte ich noch erwähnen:
- Slash Commands:
Im Grunde kann das Skript so in der Form auch für Slash-Kommandos benutzt werden. Der Input vom Slash-Command ist aber deutlich kleiner, vor allem "trigger_word" entfällt. Die Überprüfung muss man dann mit "text" machen.
Wenn ich es richtig verstanden habe, sind die Antworten für Outgoing Webhooks für alle User eines Channels sichtbar, egal ob mit @User benachrichtigt oder nicht. Antworten von Slash-Commands sieht nur derjenige, der den Befehl geschickt hat. Dieses Verhalten ist auf jeden Fall interessant für diverse Anwendungen.
Ich bin mir noch nicht sicher, ob ich die Verarbeitung auf 2 Skripte aufteile oder nicht. Ich melde mich auf jeden Fall wieder, wenn ich mich entschieden und dieses Skript überarbeitet habe.

- Sicherheit:
Ich kenne mich leider überhaupt nicht aus, was die Absicherung von Webservern angeht. Ich habe eine Token-Verifizierung mit ins Skript geschrieben, nach dem das Skript einen exit mit Fehlermeldung ausführen sollte, wenn kein passender Token übermittelt wurde.
Aber wenn das Skript so offen auf nem Webserver liegt, kann dann nicht jeder den Inhalt des Skripts einsehen und die Tokens auslesen? Und wenn man die Tokens in eine Extra-Datei schreibt, auf die die http-Gruppe keinen Zugriff hat, kann dieses Skript sie dann trd lesen? Das muss ich noch herausfinden.
Da hier ja u.a. auch Shell-Skripte aufgerufen werden, und damit exec für PHP aktiviert werden muss, halte ich es für das Beste, diese Skripte auf ein zweites Gerät mit Webserver (z.B. Raspi) zu tun, welches nur im Heimnetz erreichbar ist. Die Syno kann ja auch im internen Netz Webhooks senden und empfangen.

Grüße
 

Manticoreer

Gesperrt
Mitglied seit
27. Mai 2017
Beiträge
414
Punkte für Reaktionen
1
Punkte
0
Es gab ja gestern ein Update für den Chat, allerdings verstehe ich bei der Bot Geschichte nur Bahnhof! Als Idee käme mir, das eine neue Datei in einem bestimmten Verzeichnis hinzugefügt wurde. Also kongrett in der Videostation. Wäre sowas machbar? Und das ganze soll dann in einem bestimmten „Movie-Junkies“-Chat zu sehn sein. Das Super Goodie wäre samt Downloadlink. :eek:

Beispiel: Avatar.mp4 wurde zur Videostation hinzugefügt! Link: externe.dyndns.oderquickconnect.to/Avatar.mp4

So oder so ähnlich stelle ich mir das vor, falls sowas überhaupt funktioniert?!?
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Hi,

Version 2.0 von Synology Chat bringt das ganze meiner Vision schon deutlich näher. Hier einmal der Changelog zur Info:

Version: 2.0.0-1124

(2017-12-14)
What’s new
1. Officially supports Chat desktop application.
2. Supports poll creation to timely collect feedback from the members in a channel or conversation.
3. Supports message scheduling to automatically send messages on the scheduled time.
4. Supports message forwarding to effortlessly forward messages to one or multiple channels and conversations.
5. Supports message reminders, allowing you to receive push notifications on important messages at specific time.
6. Supports message threads, allowing you to directly comment on specific messages and receive notifications on the latest updates*regarding the subscribed and joined threads.
7. Supports bot integration, allowing one-to-one conversations*with bots to assist enterprises in automated information management.
8. Supports third-party video conference applications including appear.in, Jitsi, and JumpChat, allowing you to start video conferences in channels or conversations through slash commands.
9. Supports Google Safe Browsing to automatically check the safety of the posted URLs and trigger warnings for unsafe URLs.
10. Supports editing the messages that were not successfully sent during network disconnection, and allowing them to be manually resent after network reconnection.
11. Supports turning off an URL’s thumbnail prefetching function.
12. Supports administrators to search and delete messages in Chat Admin Console.
13. Supports Webhook messaging logs in Chat Admin Console.
Improvements
1. Enhanced the performance of channel switching.
Fixed issues
1. Fixed an issue where the list of members might not be correctly displayed after a conversation is upgraded to private channel.
2. Fixed an issue where emoji name might be incorrect.
3. Fixed an issue where the message notifications from Chat*plugins*might not be correctly shown.
4. Fixed an issue where importing Slack information containing Chinese channel name might fail.
5. Fixed an issue where accounts with invalid email address might not be able to login successfully. **
6. Fixed SSRF security issues.
7. Fixed a*security vulnerability regarding XSS (CWE-79).
8. Fixed an issue where users’ disabled/ enabled status might be abnormal after switching the account system.
9. Minor bug fixes.
Upgrade
1. To enhance*the performance of the Lookup feature offered for administrators, the message database will be updated during the upgrade and thus longer processing time may be required.

Nun kann man “ChatBots” anlegen, mit denen man in einem gesonderten Chatraum 1-zu-1 interagieren kann. Diese kombinieren lediglich die Funktion der Incoming und Outgoind Webhooks, können also mit der generierten Incoming-URL in “Eigeninitiative” Nachrichten senden und mit einer hinterlegten Outgoing-URL Nachrichten des Users verarbeiten (nettes Detail: Wenn keine Outgoing-URL hinterlegt ist, kann man nicht in den Chat schreiben. Das stellt klar, dass es sich bei dem Bot um eine “Einbahnstraße” handelt).

2 Probleme habe ich beim ersten Kurztest mit dem ChatBot festgestellt:
- Incoming Webhooks, so wie ich sie im ersten Post beschrieben habe, funktionieren nicht. Warum habe ich noch nicht rausgefunden. Die Response sieht folgendermaßen aus:
Rich (BBCode):
{"error":{"code":800,"errors":"no target"},"success":false}

- Outgoing Webhooks funktionieren grundsätzlich, allerdings sieht der POST Request wieder etwas anders aus. Vorallem fehlt das “trigger_word”, welche(s) man für ChatBots scheinbar nicht definieren kann. Das bedeutet, dass mein Skript weiter oben nicht funktioniert. Man muss dann wie bei Slash-Commands den “text” Parameter verarbeiten, was meiner Meinung nach deutlich fehleranfälliger ist. Man bräuchte eine deutlich robustere Verarbeitung des Parameters im Skript als ich es im Moment habe. Für den Anfang könnte man eine Error-Message mit Infos zur Benutzung des Bots raussenden, wenn man mit dem Text keine Methode getroffen hat. Folgendermaßen sieht jedenfalls der POST Request eines ChatBots aus:
Rich (BBCode):
token=XXXXXXXXXXXXXXXXXXXXXXXX&
user_id=3&
username=admin&
post_id=42949672964&
thread_id=0&
timestamp=1513342388474&
text=Test

@Manticoreer:
Die gleiche Idee ist mir diese Woche auch gekommen :D keine Ahnung, warum mir sowas nicht schon früher eingefallen ist.

Grundsätzlich ist das auf jeden Fall möglich. Die Frage ist nur, wie “schick” man das gelöst bekommt. Für ein vollautomatisches Erkennen neuer Filme bräuchte man einen Trigger vom Synoindex-Dienst, wenn dieser eine neue Datei im Film-Ordner indiziert hat. Falls da jemand einen Ansatz hat: Immer her damit^^
Alternativ müsste man per Skript einen eigenen Folderwatch erstellen, was ich persönlich schon nicht mehr so schick finde.

Mein Ansatz ist folgender:
Ich wende auf jeden neuen Film ein FileBot-Skript an, was mir den Film in die gewünschte Datenstruktur bringt und Cover- und Film-Informationen runterlädt. Da ist es ja nur naheliegend, dass dieses Skript dann auch die Chatnachricht raushaut. An Download-Links habe ich dabei nicht gedacht, weil der Download von Filmen bei mir gesperrt ist. Ich hätte allerdings gerne noch das Film-Cover in der Nachricht :D Dafür müsste mir FileBot die URL rausgeben, von der er das Cover geladen hat. Ob das geht weiß ich noch nicht.

Spätestens nach den Feiertagen werde ich nochmal in das Thema abtauchen. Erstmal gucken, ob ich die Probleme mit dem neuen ChatBot gelöst kriege und anschließend die Film-Geschichte ausprobieren. Ich melde mich wieder, wenn ich was Neues habe.

Grüße
 

Manticoreer

Gesperrt
Mitglied seit
27. Mai 2017
Beiträge
414
Punkte für Reaktionen
1
Punkte
0
Huhu, also ich wünsch dir auch Frohe Weihnachten und ein guten Flutsch und so! ;)

Also ich versteh eben das "System" dahinter nicht, und ich kann auch nicht scripten! Deswegen verstehe ich auch nur die hälfte davon! Auch weiß ich nicht wo ich das wie machen muss! Ausser eben das was ich im Chat vor der Nase hab! Die Chat Console ist beispielsweise mehr ??? als sonst etwas!
 

rednag

Benutzer
Mitglied seit
08. Nov 2013
Beiträge
3.954
Punkte für Reaktionen
11
Punkte
104
Wieder Welle machen @Manticoreer? :eek:
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24

Manticoreer

Gesperrt
Mitglied seit
27. Mai 2017
Beiträge
414
Punkte für Reaktionen
1
Punkte
0

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Ihr macht das schon :D

Ich hab noch einen Nachtrag zu meinen oben genannten Problemen:
Ich habe noch dieses Support-Dokument gefunden, wo die neue ChatBot-Funktion genauer erklärt wird.
Damit konnte ich das Problem der Incoming Webhooks lösen. Da steht, dass das payload zusätzlich den Parameter "user_ids" enthalten muss, damit der ChatBot weiß, an welchen Nutzer er die Nachricht senden soll.
Man kann mehrere user_ids mit Komma getrennt angeben, das sieht dann so aus:

Rich (BBCode):
payload={"text": "Dies ist eine Testnachricht", "user_ids": [1,2,3,4]}

Die user_ids legt Synology Chat selber an, und die sind ein bisschen tricky rauszufinden. Ganz unten im Support-Dokument sind URLs beschrieben, mit denen man sich für den Bot sichtbare Funktionen anzeigen lassen kann. Wenn man sich dort mit dem Token und seiner Adresse den Link für "Die für einen Bot sichtbaren Benutzer" zusammenbastelt sieht man die user_ids der einzelnen Chat-User.

Grüße
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Hi,

Ich melde mich zurück mit einem neuen Zwischenstand.
Ich habe mein Skript für Incoming Webhooks etwas überarbeitet und für die neue Bot-Funktion angepasst (wo "user_ids" im payload stehen muss).
Meine frühere Benennung ist jetzt etwas verwirrend, weil es "Bots" jetzt als eigene Funktion gibt. Man muss jetzt also unterscheiden zwischen:
  • Incoming Webhooks
  • Outgoing Webhooks
  • Slash-Commands
  • Bots

Und ich habe verschiedene Instanzen von Incoming Webhooks als Bots bezeichnet.
Hin wie her, die Verwendung hat sich von Incoming Webhooks zu Bots etwas geändert. Was bei Incoming Webhooks so war:
Rich (BBCode):
php webhook_inc.php [Bot] [Message Type] (Content)
Geht bei Bots jetzt so:
Rich (BBCode):
php chatbot_inc.php [User] [Message Type] (Content)

Das Konzept ist jetzt so, dass man für jeden Bot eigene Skripte hat, 2 Stück für Incoming und Outgoing Webhooks. Es gibt also in einem Skript nur noch eine URL bzw. einen Token. Die User werden jetzt mit ihren zugehörigen IDs (wie oben beschrieben) in ein Array eingetragen und dem Befehl als Argument mitgegeben, damit man on-the-fly auswählen kann, welche User die Nachricht bekommen sollen. Das ist bei Bots auch zwingend notwendig, weil er sonst nicht weiß, für wen die Nachricht ist. Bei Incoming Webhooks in Channels ging es bei den Usern nur darum, wer eine Push-Nachricht bekommen soll oder nicht.

Hier der Code:
PHP:
<?php

/*
chatbot_inc.php

Handling Incoming Webhooks for Bots in Synology Chat. Written for PHP 5.4 and above.
Usage:
php chatbot_inc.php <User> <Message Type> [Content]
*/

// ChatBot URL
$url = "https://example.org/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=XXXXXXXXXXXXXXXXXX";

// Device name for message integration
$source = "Syno1"; //Syno2, RaspPi, etc.

// Chat users to address, so they get Push Notifications. @channel equals every member in a channel
$Users = [
	all => "1,2,3",
	User1 => "1",
	User2 => "2",
	User3 => "3"
];

function send_chat($webhook, $message) { 
    $opts = ['http' => 
				[ 
     			   'method'  => 'POST', 
      			 'content' => $message, 
				],
				'ssl' =>
				[
					'verify_peer' => true //Toggle SSL-verification
				],
	]; 
    $context = stream_context_create($opts); 
    return file_get_contents($webhook, false, $context); 
}

function set_payload($Text) {
	$a = 'payload={"text": "' . $Text . '"'; //Incomplete. user_id gets appended later
	return $a;
}


function set_payload_long($Type, $Text) {
	switch ($Type) {
		case "Text":
			$b = set_payload($Text);
			break;
		case "URL":
			$b = 'Link <"' . $Text . '">';
			$b = set_payload($b);
			break;
		case "File":
			$b = 'payload={"text": "File Link", "file_url": "' . $Text . '"';
			break;
		default:
			$b = error_message($Type, true);
			echo 'Error: Second Argument invalid: <Text>,<URL> or <File> allowed. Usage: php chatbot_inc.php <User> <Message Type> [Content]';
			echo "\n";
	}
	return $b;
}

//Pre-definded messages. Add new Messages to your liking.
function pre_build($Type) {
	global $Users;
	global $source;
	switch($Type) {
		case "Message1":
			$c = 'Dies ist eine Testnachricht';
			$c = set_payload($c);
			break;
		case "Message2":
			$c = 'Dies ist eine zweite Testnachricht';
			$c = set_payload($c);
			break;
		case "Bootup":
			$c = $source . ' ist hochgefahren';
			$c = set_payload($c);
			break;
		case "Shutdown":
			$c = $source . ' fährt herunter';
			$c = set_payload($c);
			break;
		default:
			$c = error_message($Type, false);
	}
	return $c;
}


function error_message($fault, $notify) {
	global $source;
	if($notify) {
		$d = "'" . $fault . "' from "  . $source . ": Interpretation failed";
		$d = set_payload($d);
	} else {
		$d = NULL;
	}
	echo "Error: Invalid arguments. See chatbot_inc.php for pre-defined messages";
	echo "\n";
	return $d;
}

// Case insensitive User-Check
$ID = NULL;
foreach ($Users as $key => $value) {
	if (strcasecmp($argv[1], $key) == 0) {
		$ID = $value;
		break;
	}
}
if ($ID == NULL) {
	echo 'Error: ' . $argv[1] . ': User not found';
	echo "\n"; 
	exit;
}
// end User-Check

if(isset($argv[3])) {
	$payload = set_payload_long($argv[2], $argv[3]);
	$payload = $payload . ', "user_ids": [' . $ID . ']}'; //User ID added to payload
	send_chat($url, $payload);
} elseif (isset($argv[1]) && isset($argv[2])) {
	$payload = pre_build($argv[2]);
	$payload = $payload . ', "user_ids": [' . $ID . ']}'; //User ID added to payload
	send_chat($url, $payload);
} else {
	echo 'Error: Invalid arguments. See chatbot_inc.php for usage.';
	echo "\n";
}

?>

Grüße

P.S. Ich habe die Benachrichtigung für neue Filme ebenfalls umsetzen können. Die Umsetzung ist aber etwas komplizierter und sehr speziell. Es wird im Grunde manuell ausgelöst und ist stark abhängig von FileBot und seiner Skript Engine. Falls es dafür hier eine Zielgruppe gibt kann ich das gerne mal ausführen, andernfalls kann ich mir die Arbeit sparen^^ Mit dem Ergebnis bin jedenfalls sehr zufrieden:
synobot_movie.jpg
 

rednag

Benutzer
Mitglied seit
08. Nov 2013
Beiträge
3.954
Punkte für Reaktionen
11
Punkte
104
Als Idee käme mir, das eine neue Datei in einem bestimmten Verzeichnis hinzugefügt wurde. Also kongrett in der Videostation. Wäre sowas machbar? Und das ganze soll dann in einem bestimmten „Movie-Junkies“-Chat zu sehn sein. Das Super Goodie wäre samt Downloadlink. :eek:

Das wäre dafür doch passend oder?
 

blurrrr

Benutzer
Sehr erfahren
Mitglied seit
23. Jan 2012
Beiträge
6.204
Punkte für Reaktionen
1.103
Punkte
248
Soweit ich weiss, ist der Chat "slack"-basiert (leider nicht xmpp wie erhofft), vielleicht hilft auch eine Suche in dieser Ecke weiter, wobei komplett selbst schreiben natürlich schon wesentlich schöner ist :eek:
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
@rednag:
Nicht wirklich. Die Ansätze sind so verschieden, dass sich kaum etwas übertragen lässt.

Bei meiner Variante führe ich auf meinem Laptop händisch ein Shell Skript aus, welches die Film-Datei mit dem Programm FileBot identifiziert, benennt und Infos in eine tsv Datei lädt. Diese Infos werden dann ausgelesen, um den tmdb Link für die Nachricht zu generieren. Hier werden keine Trigger benötigt, weil es nicht automatisiert ist.

Bei dem Ansatz von Manticoreer habe ich keine Ahnung, wie man einen Trigger bekommen kann, wenn ein neuer Film hinzugefügt wird, geschweige denn der DS einen Download-Link dafür zu entlocken. Vielleicht lässt sich das über die der VideoStation zugrunde liegende Postgres-Datenbank lösen, damit kenne ich mich aber auch nicht wirklich aus. Wenn da jemand eine Lösung hat liefere ich gerne den Webhook-Part dazu :)

@blurrrr:
Danke für die Info! Wird vielleicht in Zukunft nochmal relevant. Im Moment bin ich genug damit beschäftigt, die Webhook-API zu verstehen und auszunutzen :D

Grüße
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Hi,

Es gibt wieder ein paar Neuigkeiten, von denen ich absolut begeistert bin.
Ich habe mir in den letzten Wochen einen richtigen Use-Case für meine Skripte geschaffen, indem zu Weihnachten eine neue Synology gekauft wurde und dort jetzt eine virtuelle Maschine mit FHEM läuft.
Mit FHEM kann ich endlich meine Datensammelwut richtig ausleben, was sehr viel Spaß macht - kann ich nur empfehlen :D
Jedenfalls will ich sowas sensibles wie FHEM nicht in Richtung Internet öffnen, deswegen soll der Synology Chat als Schnittstelle zu FHEM dienen.

Ich habe nun ein Skript für Outgoing-Webhooks eines Bots geschrieben, welches mir auf Anfrage Informationen aus der MySQL-Datenbank von FHEM holt und als Antwort zurückschickt.
Hier der Code:
PHP:
<?php
/* PHP-Script for Outgoing Webhooks from Bots in Synology Chat
Tested under PHP 5.4 and PHP 7.0
Usage:
- Setup an Bot in Synology Chat. Documentation can be found here: https://www.synology.com/en-global/knowledgebase/DSM/help/Chat/chat_integration
	- Set the Outgoing URL to this script, for example: https://my-address.com/chatbot_out.php
	- Add the Token in $Bots array below.

Example of POST data fields for Bot Outgoing Webhook:
token=XXXXXXXXXXXX&user_id=3&username=admin&post_id=68719476738&thread_id=0&timestamp=1516099478578&text=Testnachricht
*/

//Token Verification
function verify($token) {
	$Bots = [
		"Test" => "XXXXXXXXXXXX",
		"Bot2" => "XXXXXXXXXXXXX"
	];
	foreach ($Bots as $key => $value) {
		if ($token == $value) {
			// echo "Token gefunden";
			return;
		}
	}
	exit("unable to verify Chat token");
}

function db_get($device, $reading) {
	require 'db.php';
	$erg = $db->query("SELECT VALUE FROM current WHERE DEVICE='" . $device . "' && READING='" . $reading . "'");
	$arr = $erg->fetch_assoc();
	$output = $arr['VALUE'];
	$erg->free;
	$db->close;
	return $output;
}

$input = file_get_contents('php://input');

//input example for testing
//$input = 'token=XXXXXXXXXXXX&user_id=3&username=admin&post_id=68719476738&thread_id=0&timestamp=1516099478578&text=Testnachricht';
parse_str($input, $data); //Creates array with POST data fields from the request
$textex = explode(" ", $data["text"]); //array with every word in "text" data field
$text = $data["text"];

verify($data["token"]);


switch ($text) {
	case ($text == "Test" || $text == "test"):
		$output = "Webhook-Test erfolgreich!";
		break;
	case ($text == "Status" || $text == "status"):
		$output = "Placeholder status";
		break;
	case ($text == "Spritpreis" || $text == "spritpreis"):
		$output = "Tankstelle JET\nSuper: " . db_get("Spritpreis","superprice") . " €\nDiesel: " . db_get("Spritpreis","dieselprice") . " €";
		break;
	case ($text == "Temperatur" || $text == "temperatur"):
		$output = "Außentemperatur: " . db_get("Wetter","aussentemp-get") . " °C\nInnentemperatur: " . db_get("K_Temperatur","state") . " °C";
		break;
	case "help":
		$output = "Folgende Befehle versteht der Bot:\n- Test: Funktionstest\n- Status: Kommt noch\n- Spritpreis: Aktueller Spritpreis JET\n- Temperatur: Außen und Innentemperatur\n- help: Diese Hilfe";
		break;
}

$context = [ 'text' => $output ];
header('Content-Type: application/json');
echo json_encode($context);
?>
und die dazu referenzierte db.php:
PHP:
<?php
error_reporting(E_ALL); //0 or E_ALL for (de)activating error reporting
$db = new mysqli('localhost', 'USER', 'PASSWORD', 'DATABASE');
$db->set_charset('utf8');

if ($db->connect_errno) {
	    die('Problem aufgetreten');
}
?>

Interessant ist hier vor allem die Funktion db_get zusammen mit db.php, welche den Zugriff auf MySQL ermöglichen. Für Profis bestimmt eine lächerlich einfache Umsetzung, aber ich hoffe trotzdem, dass es jemandem weiterhilft.
Die Datei liegt nun auf dem php-fähigen WebServer der virtuellen Maschine und die Syno sendet den Request übers lokale Netwerk.

Das fertige Produkt sieht dann so aus:
2018-01-16_FHEM_Chat.jpg

Das wars dann auch schon wieder. Wie immer würde ich mich über Verbesserungsvorschläge oder anderen Input freuen.

Grüße

P.S.: Als nächstes Projekt möchte ich dieses php-Skript ergänzen, um bestimmte Nachrichten in MQTT einzuspeisen. Ich habe vorher noch nicht mit MQTT gearbeitet, aber was ich bis jetzt gelesen habe, sind die Möglichkeiten zum Auslesen oder erhalten von Werten quasi unbegrenzt. Ich möchte darüber Werte in FHEM setzen, um auch Geräte zu steuern und nicht nur Informationen abzuholen. Mal schauen, ob das klappt.
 
Zuletzt bearbeitet:

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
So, die Implementation von MQTT habe ich auch umsetzen können. Auf github findet man das Skript phpMQTT, was eine Klasse für die Kopplung bereitstellt. Das macht es denkbar einfach. Ich musste den Namespace in Zeile 3 löschen, damit ich es unter PHP 7 einbinden konnte, keine Ahnung warum. Ein Aufruf könnte dann so aussehen:

PHP:
function mq_pub($room, $device, $bin) {
	require_once 'phpMQTT.php';
	$mqtt = new phpMQTT('localhost', 1883, 'SynoChat'); 				//new MQTT($IP-Address, $PORT, $DeviceID)
	if ($mqtt->connect(false, NULL, ‘USER’, ‘PASSWORD’)) {			//connect($clean, $will, $USERNAME, $PASSWORD)
		$mqtt->publish("haus/" . $room . "/" . $device, $bin, 0);			//publish($TOPIC, $CONTENT, $QoS)
		$mqtt->close();
	} else { exit(1); }
}

$input = file_get_contents('php://input');	//get POST data from Webhook
parse_str($input, $data); 				//Creates array with POST data fields from the request
$textex = explode(" ", $data["text"]); 		//array with every word in "text" data field

switch(textex[1]) {
	case "Wohnzimmerlampe":
		if (textex[2] = “an”) {
			mq_pub("wohnzimmer", "lampe", 1);
			$output = "Wohnzimmerlampe eingeschaltet";
		} elseif (textex[2] = "aus") {
			mq_pub("wohnzimmer", "lampe", 0);
			$output = "Wohnzimmerlampe ausgeschaltet";
		}
	}
}

$context = [ 'text' => $output ];
header('Content-Type: application/json');
echo json_encode($context);

In dem Beispiel würde der Text “Wohnzimmerlampe an” den Wert 1 in das Topic “haus/wohnzimmer/lampe” schreiben, wo dann hoffentlich ein MQTT-fähiger Aktor drauf lauscht. So wäre man auf keine andere SmartHome Software angewiesen.
Bei mir publishe ich einfach den gesamten text-String in das Topic “chat/USERNAME”, auf den meine FHEM-Installation lauscht. Die weitere Auswertung und Schaltung mache ich dann dort.

So, damit ist das Thema Chatbot für mich auch abgeschlossen. Meine beschriebenen Anwendungen sind doch leider sehr speziell, das ist wahrscheinlich auch der Grund, warum der Thread hier eher ein Blog geworden ist. Ich hoffe trd, dass irgendwann irgendwer nen Nutzen davon hat.

Grüße
 

PsychoHH

Benutzer
Mitglied seit
03. Jul 2013
Beiträge
2.967
Punkte für Reaktionen
4
Punkte
78
Lese das hier gerade zum ersten Mal und denke nur so hammer.
Hast du mal ein whoami per Shell getestet? Würde mich mal interessieren welche Rechte der bot per default hat.
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Hi,

Evtl. habe ich die Frage nicht richtig verstanden, aber der Bot im Sinne eines Users auf der Shell existiert nicht. Die gesamte Chat-Integration basiert auf HTTP-POST Requests. Bei Incoming Webhooks wird nichts weiter gemacht, als den übermittelten Payload in den Chat zu schreiben, und bei Outgoing Webhooks stellt der Chat eine POST-Anfrage an eine selbst festgelegte URL. In dem Moment zählt er dann als normaler User eines Web-Services, sprich er hat z.B. auf der DiskStation die Rechte der Gruppe http.

Grüße
 

Hoffy

Benutzer
Mitglied seit
16. Jan 2014
Beiträge
241
Punkte für Reaktionen
1
Punkte
24
Hi,

Ich melde mich zurück mit neu strukturierten Skripten und einem neuen Anwendungsbeispiel. Nun sind auch interaktive Nachrichten mit Buttons möglich.

Mittlerweile sind bei mir mehrere Bots im Einsatz. Mit jedem neuen habe ich kleine Verbesserungen an bestehenden Funktionen vorgenommen und musste diese immer in alle anderen übernehmen.
Deswegen habe ich jetzt alle allgemeinen Funktionen in ein separates Skript myUtils.php ausgelagert, wo alle Bot-spezifischen Skripte drauf zugreifen.

Mein Ordner sieht jetzt also so aus:
Rich (BBCode):
/ChatBot
	myUtils.php
	phpMQTT.php
	db.php

	fhembot_inc.php
	fhembot_out.php
	
	mediabot_inc.php
	mediabot_out.php
	
	webhook_inc.php
	webhook_out.php

Symbolische Links für die *_out Skripte und myUtils liegen dann auf einem Webserver, den der Chat für Anfragen anspricht.

Und hier sind Beispiele der überarbeiteten Skripte:
Anhang anzeigen ChatBot.zip

Zu den Button Actions:
Hier wird beschrieben, wie das JSON Payload aufgebaut sein muss, um Buttons zu erzeugen.Im *_inc Skript sind Beispiele dafür.
Wenn man einen Button drückt, wird erneut ein POST Request an die URL für Outgoing Webhooks gesendet. Deswegen muss das *_out Skript jetzt unterscheiden, ob der Request durch einen gesendeten Text oder einen Button erzeugt wurde.
Der Payload eines Button-press sieht wieder etwas anders aus als alle anderen, ein Beispiel dafür steht im *_out Skript. Das Payload wird zerlegt, um die “callback_id” und “value” Werte auszulesen, die man beim erzeugen des Button festgelegt hat. Ist alles ein bisschen verwirrend, deswegen möchte ich es anhand eines Beispiels zeigen:

Und zwar habe ich eine Suchfunktion für meine Filmsammlung erstellt. Mit dem Text “Film Avatar” wird nach dem Film gesucht und die Ergebnisse werden zurückgeschickt.
Im bot_out.php Skript kümmert sich die Funktion kodijsmovie() darum. Diese ruft mithilfe von texturecache.py die Infos von einer laufenden Kodi Instanz als JSON ab, verarbeitet sie und schickt sie an den Chat zurück.

Wenn diese nun einen Treffer gefunden hat, wird folgende Nachricht mit Buttons erzeugt:
PHP:
$a = ['text' => "Film gefunden!\nLogan Lucky",
	'attachments' => [[
		'callback_id' => 'Movie Logan Lucky',
		'text' => 'Logan Lucky',
		'actions' => [
			[
			'type' => 'button',
			'name' => 'resp1',
			'value' => 'ok',
			'text' => 'OK',
			'style' => 'green'
			],[
			'type' => 'button',
			'name' => 'resp2',
			'value' => 'info',
			'text' => 'Weitere Informationen',
			'style' => 'green'
			]]
		]]
	];

print_r(json_encode($a));
// {"text":"Film gefunden!\nLogan Lucky","attachments":[{"callback_id":"Movie Logan Lucky","text":"Logan Lucky","actions":[{"type":"button","name":"resp1","value":"ok","text":"OK","style":"green"},{"type":"button","name":"resp2","value":"info","text":"Weitere Informationen","style":"green"}]}]}

Wichtig ist, dass “attachments” und “actions” Arrays enthalten, deswegen die vielen Klammern. So sind pro Nachricht mehrere Textfelder mit jeweils mehreren Buttons möglich
Die Nachricht sieht dann so aus (ganz unten):
sc1.jpg
Wenn man nun einen der Button drückt, wird der neue Request gesendet, völlig unabhängig vom ersten. Deswegen muss man über die callback_id die Art des Requests und über den value Wert den gedrückten Button ermitteln. Im bot_out Skript steht die Verarbeitung für dieses Beispiel drin. Auf den Button-press kann man wiederum mit einem payload der Form {“text”: “Button gedrückt”} reagieren. Das sieht für das Beispiel so aus:
sc2.jpg

Hoffe, das hilft jemandem weiter. Fragen dazu beantworte ich gerne.

Grüße
 
  • Like
Reaktionen: sub2010
Status
Für weitere Antworten geschlossen.
 

Kaffeautomat

Wenn du das Forum hilfreich findest oder uns unterstützen möchtest, dann gib uns doch einfach einen Kaffee aus.

Als Dankeschön schalten wir deinen Account werbefrei.

:coffee:

Hier gehts zum Kaffeeautomat