mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 06:39:11 +00:00
3e127e4505
git-svn-id: https://svn.fhem.de/fhem/trunk@12219 2b470e98-0d58-463d-a4d8-8e2adae1ed80
658 lines
16 KiB
Perl
658 lines
16 KiB
Perl
|
|
# $Id$
|
|
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Socket;
|
|
use IO::Handle;
|
|
|
|
sub
|
|
yowsup_Initialize($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
$hash->{ReadFn} = "yowsup_Read";
|
|
|
|
$hash->{DefFn} = "yowsup_Define";
|
|
$hash->{NotifyFn} = "yowsup_Notify";
|
|
$hash->{UndefFn} = "yowsup_Undefine";
|
|
$hash->{ShutdownFn} = "yowsup_Shutdown";
|
|
$hash->{SetFn} = "yowsup_Set";
|
|
#$hash->{GetFn} = "yowsup_Get";
|
|
$hash->{AttrFn} = "yowsup_Attr";
|
|
$hash->{AttrList} = "disable:1 ";
|
|
$hash->{AttrList} .= "cmd home nickname ". $readingFnAttributes;
|
|
}
|
|
|
|
#####################################
|
|
|
|
sub
|
|
yowsup_Define($$)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
|
|
return "Usage: define <name> yowsup" if(@a < 2);
|
|
|
|
my $name = $a[0];
|
|
my $number = $a[2];
|
|
|
|
if( !defined($number) ) {
|
|
my $d = $modules{yowsup}{defptr}{yowsup};
|
|
return "yowsup MASTER already defined as $d->{NAME}." if( defined($d) && $d->{NAME} ne $name );
|
|
|
|
$modules{yowsup}{defptr}{yowsup} = $hash;
|
|
|
|
addToDevAttrList( $name, "acceptFrom" );
|
|
|
|
} else {
|
|
return "no yowsup MASTER defined." if( !defined($modules{yowsup}{defptr}{yowsup}) );
|
|
|
|
my $d = $modules{yowsup}{defptr}{$number};
|
|
return "yowsup $number already defined as $d->{NAME}." if( defined($d) && $d->{NAME} ne $name );
|
|
|
|
$modules{yowsup}{defptr}{$number} = $hash;
|
|
|
|
addToDevAttrList( $name, "commandPrefix" );
|
|
addToDevAttrList( $name, "allowedCommands" );
|
|
|
|
addToDevAttrList( $name, "acceptFrom" ) if( $number =~ m/\./ );
|
|
|
|
$hash->{NUMBER} = $number;
|
|
}
|
|
|
|
$hash->{NAME} = $name;
|
|
|
|
$hash->{NOTIFYDEV} = "global";
|
|
|
|
if( $init_done ) {
|
|
yowsup_Disconnect($hash);
|
|
yowsup_Connect($hash);
|
|
} elsif( $hash->{STATE} ne "???" ) {
|
|
$hash->{STATE} = "Initialized";
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub
|
|
yowsup_Notify($$)
|
|
{
|
|
my ($hash,$dev) = @_;
|
|
|
|
return if($dev->{NAME} ne "global");
|
|
return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
|
|
|
|
yowsup_Disconnect($hash);
|
|
yowsup_Connect($hash);
|
|
}
|
|
|
|
sub
|
|
yowsup_reConnect($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 3, "$name: reConnect";
|
|
|
|
yowsup_Disconnect($hash);
|
|
yowsup_Connect($hash);
|
|
}
|
|
|
|
sub
|
|
yowsup_Connect($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
return undef if( $hash->{NUMBER} );
|
|
|
|
return undef if( AttrVal($name, "disable", 0 ) == 1 );
|
|
|
|
$hash->{PARTIAL} = "";
|
|
|
|
my ($yowsup_child, $parent);
|
|
if( socketpair($yowsup_child, $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) {
|
|
$yowsup_child->autoflush(1);
|
|
$parent->autoflush(1);
|
|
|
|
my $pid = fhemFork();
|
|
|
|
if(!defined($pid)) {
|
|
close $parent;
|
|
close $yowsup_child;
|
|
|
|
my $msg = "$name: Cannot fork: $!";
|
|
Log 1, $msg;
|
|
return $msg;
|
|
}
|
|
|
|
if( $pid ) {
|
|
close $parent;
|
|
|
|
$hash->{STATE} = "Connected";
|
|
$hash->{CONNECTS}++;
|
|
|
|
$hash->{FH} = $yowsup_child;
|
|
$hash->{FD} = fileno($yowsup_child);
|
|
$hash->{PID} = $pid;
|
|
|
|
$hash->{WAITING_FOR_LOGIN} = 1;
|
|
|
|
$selectlist{$name} = $hash;
|
|
|
|
} else {
|
|
close $yowsup_child;
|
|
|
|
close STDIN;
|
|
close STDOUT;
|
|
|
|
my $fn = $parent->fileno();
|
|
open(STDIN, "<&$fn") or die "can't redirect STDIN $!";
|
|
open(STDOUT, ">&$fn") or die "can't redirect STDOUT $!";
|
|
|
|
#select STDIN; $| = 1;
|
|
#select STDOUT; $| = 1;
|
|
|
|
#STDIN->autoflush(1);
|
|
STDOUT->autoflush(1);
|
|
|
|
close $parent;
|
|
|
|
$ENV{PYTHONUNBUFFERED} = 1;
|
|
|
|
if( my $home = AttrVal($name, "home", undef ) ) {
|
|
$home = $ENV{'PWD'} if( $home eq 'PWD' );
|
|
$ENV{'HOME'} = $home;
|
|
Log3 $name, 2, "$name: setting \$HOME to $home";
|
|
}
|
|
|
|
my $cmd = AttrVal($name, "cmd", "/opt/local/bin/yowsup-cli demos -c /root/config.yowsup --yowsup" );
|
|
Log3 $name, 2, "$name: starting yoswup-cli: $cmd";
|
|
|
|
exec split( ' ', $cmd ) or Log3 $name, 1, "exec failed";
|
|
|
|
Log3 $name, 1, "set the cmd attribut to: <path1>/yowsup-cli demos -c <path2>/config.yowsup --yowsup";
|
|
|
|
POSIX::_exit(0);;
|
|
}
|
|
|
|
} else {
|
|
#$hash->{STATE} = "Connected";
|
|
Log3 $name, 3, "$name: socketpair failed";
|
|
InternalTimer(gettimeofday()+20, "yowsup_Connect", $hash, 0);
|
|
}
|
|
}
|
|
|
|
sub
|
|
yowsup_Disconnect($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
return undef if( $hash->{NUMBER} );
|
|
|
|
RemoveInternalTimer($hash);
|
|
|
|
return if( !$hash->{FD} );
|
|
|
|
if( $hash->{PID} ) {
|
|
yowsup_Write($hash, '/disconnect' );
|
|
|
|
kill( 9, $hash->{PID} );
|
|
waitpid($hash->{PID}, 0);
|
|
delete $hash->{PID};
|
|
}
|
|
|
|
close($hash->{FH}) if($hash->{FH});
|
|
delete($hash->{FH});
|
|
delete($hash->{FD});
|
|
delete($selectlist{$name});
|
|
|
|
$hash->{STATE} = "Disconnected";
|
|
Log3 $name, 3, "$name: Disconnected";
|
|
$hash->{LAST_DISCONNECT} = FmtDateTime( gettimeofday() );
|
|
}
|
|
|
|
sub
|
|
yowsup_Undefine($$)
|
|
{
|
|
my ($hash, $arg) = @_;
|
|
|
|
yowsup_Disconnect($hash);
|
|
|
|
if( $hash->{NUMBER} ) {
|
|
delete $modules{yowsup}{defptr}{$hash->{NUMBER}};
|
|
} else {
|
|
delete $modules{yowsup}{defptr}{yowsup};
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
sub
|
|
yowsup_Shutdown($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
yowsup_Disconnect($hash);
|
|
|
|
return undef;
|
|
}
|
|
|
|
|
|
sub
|
|
yowsup_Set($$@)
|
|
{
|
|
my ($hash, $name, $cmd, @args) = @_;
|
|
|
|
my $list = "";
|
|
|
|
if( $hash->{NUMBER} ) {
|
|
my $phash = $modules{yowsup}{defptr}{yowsup};
|
|
$list .= "image send" if( $phash->{PID} );
|
|
|
|
if( $cmd eq 'image' ) {
|
|
return "MASTER not connected" if( !$phash->{PID} );
|
|
|
|
readingsSingleUpdate( $hash, 'sent', 'image: '. join( ' ', @args ), 1 );
|
|
|
|
my $number = $hash->{NUMBER};
|
|
$number =~ s/\./-/;
|
|
|
|
my $image = shift(@args);
|
|
|
|
return yowsup_Write( $phash, "/image send $number $image '". join( ' ', @args ) ."'" );
|
|
|
|
return undef;
|
|
} elsif( $cmd eq 'send' ) {
|
|
return "MASTER not connected" if( !$phash->{PID} );
|
|
|
|
readingsSingleUpdate( $hash, 'sent', join( ' ', @args ), 1 );
|
|
|
|
my $number = $hash->{NUMBER};
|
|
$number =~ s/\./-/;
|
|
|
|
return yowsup_Write( $phash, "/message send $number '". join( ' ', @args ) ."'" );
|
|
|
|
return undef;
|
|
}
|
|
|
|
} else {
|
|
$list .= "image send raw disconnect:noArg " if( $hash->{PID} );
|
|
$list .= "reconnect:noArg";
|
|
|
|
if( $cmd eq 'raw' ) {
|
|
return yowsup_Write( $hash, join( ' ', @args ) );
|
|
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'image' ) {
|
|
readingsSingleUpdate( $hash, 'sent', 'image: '. join( ' ', @args ), 1 );
|
|
|
|
my $number = shift(@args);
|
|
$number =~ s/\./-/;
|
|
|
|
my $image = shift(@args);
|
|
|
|
return yowsup_Write( $hash, "/image send $number $image '". join( ' ', @args ) ."'" );
|
|
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'send' ) {
|
|
readingsSingleUpdate( $hash, 'sent', join( ' ', @args ), 1 );
|
|
|
|
my $number = shift(@args);
|
|
$number =~ s/\./-/;
|
|
|
|
if( $number =~ m/,/ ) {
|
|
return yowsup_Write( $hash, "/message broadcast $number '". join( ' ', @args ) ."'" );
|
|
} else {
|
|
return yowsup_Write( $hash, "/message send $number '". join( ' ', @args ) ."'" );
|
|
}
|
|
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'disconnect' ) {
|
|
yowsup_Disconnect($hash);
|
|
|
|
return undef;
|
|
|
|
} elsif( $cmd eq 'reconnect' ) {
|
|
yowsup_Disconnect($hash);
|
|
yowsup_Connect($hash);
|
|
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
return "Unknown argument $cmd, choose one of $list";
|
|
}
|
|
|
|
|
|
sub
|
|
yowsup_Get($$@)
|
|
{
|
|
my ($hash, $name, $cmd) = @_;
|
|
|
|
my $list = "devices:noArg";
|
|
|
|
if( $cmd eq "devices" ) {
|
|
return undef;
|
|
}
|
|
|
|
return "Unknown argument $cmd, choose one of $list";
|
|
}
|
|
|
|
sub
|
|
yowsup_Parse($$)
|
|
{
|
|
my ($hash,$data) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 4, "$name: parse: $data";
|
|
|
|
$hash->{TIME} = TimeNow();
|
|
RemoveInternalTimer($hash);
|
|
InternalTimer(gettimeofday()+60*10, "yowsup_reConnect", $hash, 0);
|
|
|
|
if( $data =~ m/\[offline\]:/ ) {
|
|
readingsSingleUpdate( $hash, "state", 'offline', 1 ) if( ReadingsVal($name,'state','' ) ne 'offline' );
|
|
|
|
if( $hash->{WAITING_FOR_LOGIN} ) {
|
|
yowsup_Write( $hash, '/L' );
|
|
yowsup_Write( $hash, '/presence available' );
|
|
|
|
yowsup_Write( $hash, "/presence name '". AttrVal($name, 'nickname', "") ."'" ) if(defined(AttrVal($name, 'nickname', undef)));
|
|
|
|
#yowsup_Write( $hash, '/ping' );
|
|
|
|
delete $hash->{WAITING_FOR_LOGIN};
|
|
}
|
|
|
|
} elsif( $data =~ m/\[connected\]:/ ) {
|
|
readingsSingleUpdate( $hash, "state", 'connected', 1 ) if( ReadingsVal($name,'state','' ) ne 'connected' );
|
|
|
|
} elsif( $data =~ m/Auth: Logged in!/ ) {
|
|
readingsSingleUpdate( $hash, "state", 'logged in', 1 ) if( ReadingsVal($name,'state','' ) ne 'logged in' );
|
|
|
|
}
|
|
|
|
if( $data =~ m/^CHATSTATE:.*State: (\S*).*From: ([\d-]*)/s ) {
|
|
my $chatstate = $1;
|
|
my $number = $2;
|
|
$number =~ s/-/\./;
|
|
|
|
if( my $chash = $modules{yowsup}{defptr}{$number} ) {
|
|
readingsSingleUpdate( $chash, "chatstate", $chatstate, 1 );
|
|
}
|
|
|
|
#} elsif( $data =~ m/\[(.*)@.*\((.*)\)\]:\[(.*)\]\s*(.*)/s ) {
|
|
} elsif( $data =~ m/\[(.*)@.*\((.*)\)\]:\[([^\]]*)\]\s*(.*)(\nMessage)/s
|
|
|| $data =~ m/\[(.*)@.*\((.*)\)\]:\[([^\]]*)\]\s*(.*)/s ) {
|
|
|
|
my $number = $1;
|
|
my $time = $2;
|
|
my $id = $3;
|
|
my $message = $4;
|
|
my $last_sender;
|
|
|
|
if( $number =~ m/(\d*)\/(\d*)-(\d*)/ ) {
|
|
$number = "$2.$3";
|
|
$last_sender = $1;
|
|
}
|
|
|
|
$message =~ s/\n$//;
|
|
$message =~ s/[\b]*$//;
|
|
|
|
my $chash = $modules{yowsup}{defptr}{$number};
|
|
if( !$chash ) {
|
|
my $accept_from = AttrVal($name, "acceptFrom", undef );
|
|
if( !$accept_from || ",$accept_from," =~/,$number,/ ) {
|
|
my $define = "$number yowsup $number";
|
|
my $cmdret = CommandDefine(undef,$define);
|
|
if($cmdret) {
|
|
Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for number '$number': $cmdret";
|
|
} else {
|
|
#$cmdret = CommandAttr(undef,"$number alias ".$result->{$id}{name});
|
|
$cmdret = CommandAttr(undef,"$number room yowsup");
|
|
#$cmdret = CommandAttr(undef,"$number IODev $name");
|
|
}
|
|
|
|
$chash = $modules{yowsup}{defptr}{$number};
|
|
}
|
|
}
|
|
|
|
if( $chash ) {
|
|
readingsBeginUpdate($chash);
|
|
if( $last_sender ) {
|
|
readingsBulkUpdate( $chash, "chatstate", "received from: $last_sender" );
|
|
} else {
|
|
readingsBulkUpdate( $chash, "chatstate", "received" );
|
|
}
|
|
readingsBulkUpdate( $chash, "message", $message );
|
|
readingsEndUpdate($chash, 1);
|
|
|
|
my $cname = $chash->{NAME};
|
|
if( my $prefix = AttrVal($cname, "commandPrefix", undef ) ) {
|
|
my $cmd;
|
|
if( $prefix eq '0' ) {
|
|
} elsif( $prefix eq '1' ) {
|
|
$cmd = $message;
|
|
} elsif( $message =~ m/^$prefix(.*)/ ) {
|
|
$cmd = $1;
|
|
}
|
|
|
|
if( $cmd ) {
|
|
my $accept_from = AttrVal($cname, "acceptFrom", undef );
|
|
if( !$accept_from || $last_sender || ",$accept_from," =~/,$last_sender,/ ) {
|
|
Log3 $name, 3, "$cname: received command: $cmd";
|
|
|
|
$chash->{SNAME} = $cname;
|
|
my $ret = AnalyzeCommandChain( $chash, $cmd );
|
|
|
|
Log3 $name, 4, "$cname: command result: $ret";
|
|
|
|
my $number = $chash->{NUMBER};
|
|
$number =~ s/\./-/;
|
|
|
|
yowsup_Write( $hash, "/message send $number '$ret'" ) if( $ret );
|
|
|
|
} else {
|
|
Log3 $cname, 3, "$cname: commands: ". $last_sender?$last_sender:$number ." not allowed";
|
|
|
|
}
|
|
}
|
|
|
|
} else {
|
|
Log3 $cname, 3, "$cname: commands not allowed";
|
|
}
|
|
|
|
} else {
|
|
Log3 $name, 3, "$name: sender: $number not allowed";
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
sub
|
|
yowsup_Read($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $buf;
|
|
my $ret = sysread($hash->{FH}, $buf, 65536 );
|
|
|
|
if(!defined($ret) || $ret <= 0) {
|
|
yowsup_Disconnect( $hash );
|
|
|
|
Log3 $name, 3, "$name: read: error during sysread: $!" if(!defined($ret));
|
|
Log3 $name, 3, "$name: read: end of file reached while sysread" if( $ret <= 0);
|
|
|
|
InternalTimer(gettimeofday()+10, "yowsup_Connect", $hash, 0);
|
|
return undef;
|
|
}
|
|
|
|
yowsup_Parse($hash,$buf);
|
|
return undef;
|
|
|
|
my $data = $hash->{PARTIAL};
|
|
Log3 $name, 5, "yowsup/RAW: $data/$buf";
|
|
$data .= $buf;
|
|
|
|
$hash->{PARTIAL} = $data;
|
|
}
|
|
|
|
sub
|
|
yowsup_Write($$)
|
|
{
|
|
my ($hash, $data) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
return "not connected" if( !$hash->{PID} );
|
|
|
|
#my $ls = chr(226) . chr(128) . chr(168);
|
|
#$data =~ s/\n/$ls/g;
|
|
|
|
$data =~ s/\n/\r/g;
|
|
|
|
Log3 $name, 3, "$name: sending $data";
|
|
|
|
syswrite $hash->{FH}, $data ."\n";
|
|
|
|
return undef;
|
|
}
|
|
|
|
|
|
sub
|
|
yowsup_Attr($$$)
|
|
{
|
|
my ($cmd, $name, $attrName, @params) = @_;
|
|
my ($attrVal) = @params;
|
|
|
|
my $orig = $attrVal;
|
|
|
|
if($attrName eq "allowedCommands" && $cmd eq "set") {
|
|
my $aName = "allowed_$name";
|
|
my $exists = ($defs{$aName} ? 1 : 0);
|
|
AnalyzeCommand(undef, "defmod $aName allowed");
|
|
AnalyzeCommand(undef, "attr $aName validFor $name");
|
|
AnalyzeCommand(undef, "attr $aName $attrName ".join(" ",@params));
|
|
return "$name: ".($exists ? "modifying":"creating").
|
|
" device $aName for attribute $attrName";
|
|
|
|
} elsif( $attrName eq "disable" ) {
|
|
my $hash = $defs{$name};
|
|
yowsup_Disconnect($hash);
|
|
if( $cmd eq "set" && $attrVal ne "0" ) {
|
|
$attrVal = 1;
|
|
|
|
} else {
|
|
$attr{$name}{$attrName} = 0;
|
|
yowsup_Connect($hash);
|
|
|
|
}
|
|
} elsif( $attrName eq "cmd" ) {
|
|
my $hash = $defs{$name};
|
|
if( $cmd eq "set" ) {
|
|
$attr{$name}{$attrName} = $attrVal;
|
|
} else {
|
|
delete $attr{$name}{$attrName};
|
|
}
|
|
|
|
yowsup_Disconnect($hash);
|
|
yowsup_Connect($hash);
|
|
}
|
|
|
|
|
|
if( $cmd eq "set" ) {
|
|
if( !defined($orig) || $orig ne $attrVal ) {
|
|
$attr{$name}{$attrName} = $attrVal;
|
|
return $attrName ." set to ". $attrVal;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=item summary interface to the yowsup librbary (for whatsapp)
|
|
=item summary_DE Interface zur yowsup Bibliothek (für WhatsApp)
|
|
=begin html
|
|
|
|
<a name="yowsup"></a>
|
|
<h3>yowsup</h3>
|
|
<ul>
|
|
Module to interface to the yowsup library to send and recive WhatsApp messages.<br><br>
|
|
|
|
Notes:
|
|
<ul>
|
|
<li>Probably only works on linux/unix systems.</li>
|
|
</ul><br>
|
|
|
|
<a name="yowsup_Define"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> yowsup</code><br>
|
|
<br>
|
|
|
|
Defines a yowsup device.<br><br>
|
|
|
|
Examples:
|
|
<ul>
|
|
<code>define WhatsApp yowsup</code><br>
|
|
</ul>
|
|
</ul><br>
|
|
|
|
<a name="yowsup_Set"></a>
|
|
<b>Set</b>
|
|
<ul>
|
|
<li>image [<number>] <path> [<text>]<br>
|
|
sends an image with optional text. <number> has to be given if sending via master device.</li>
|
|
<li>send [<numner>] <text><br>
|
|
sends <text>. <number> has to be given if sending via master device.</li>
|
|
</ul><br>
|
|
|
|
<a name="yowsup_Attr"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li>cmd<br>
|
|
complette commandline to start the yowsup cli client<br>
|
|
eg: attr WhatsApp cmd /opt/local/bin/yowsup-cli demos -c /root/config.yowsup --yowsup</li>
|
|
|
|
<li>home<br>
|
|
set $HOME for the started yowsup process<br>
|
|
PWD -> set to $PWD<br>
|
|
anything else -> use as $HOME</li>
|
|
|
|
<li>nickname<br>
|
|
nickname that will be send as sender</li>
|
|
|
|
<li>acceptFrom<br>
|
|
comma separated list of contacts (numbers) from which messages will be accepted</li>
|
|
|
|
<li>commandPrefix<br>
|
|
not set -> don't accept commands<br>
|
|
0 -> don't accept commands<br>
|
|
1 -> allow commands, every message is interpreted as a fhem command<br>
|
|
anything else -> if the message starts with this prefix then everything after the prefix is taken as the command</li>
|
|
|
|
<li>allowedCommands<br>
|
|
A comma separated list of commands that are allowed from this contact.<br>
|
|
If set to an empty list <code>, (i.e. comma only)</code> no commands are accepted.<br>
|
|
<b>Note: </b>allowedCommands should work as intended, but no guarantee
|
|
can be given that there is no way to circumvent it.</li>
|
|
</ul>
|
|
</ul>
|
|
|
|
=end html
|
|
=cut
|
|
|