| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- # $Id: 32_yowsup.pm 12219 2016-09-29 10:03:25Z justme1968 $
- 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
|