32_yowsup.pm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. # $Id: 32_yowsup.pm 12219 2016-09-29 10:03:25Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Socket;
  6. use IO::Handle;
  7. sub
  8. yowsup_Initialize($)
  9. {
  10. my ($hash) = @_;
  11. $hash->{ReadFn} = "yowsup_Read";
  12. $hash->{DefFn} = "yowsup_Define";
  13. $hash->{NotifyFn} = "yowsup_Notify";
  14. $hash->{UndefFn} = "yowsup_Undefine";
  15. $hash->{ShutdownFn} = "yowsup_Shutdown";
  16. $hash->{SetFn} = "yowsup_Set";
  17. #$hash->{GetFn} = "yowsup_Get";
  18. $hash->{AttrFn} = "yowsup_Attr";
  19. $hash->{AttrList} = "disable:1 ";
  20. $hash->{AttrList} .= "cmd home nickname ". $readingFnAttributes;
  21. }
  22. #####################################
  23. sub
  24. yowsup_Define($$)
  25. {
  26. my ($hash, $def) = @_;
  27. my @a = split("[ \t][ \t]*", $def);
  28. return "Usage: define <name> yowsup" if(@a < 2);
  29. my $name = $a[0];
  30. my $number = $a[2];
  31. if( !defined($number) ) {
  32. my $d = $modules{yowsup}{defptr}{yowsup};
  33. return "yowsup MASTER already defined as $d->{NAME}." if( defined($d) && $d->{NAME} ne $name );
  34. $modules{yowsup}{defptr}{yowsup} = $hash;
  35. addToDevAttrList( $name, "acceptFrom" );
  36. } else {
  37. return "no yowsup MASTER defined." if( !defined($modules{yowsup}{defptr}{yowsup}) );
  38. my $d = $modules{yowsup}{defptr}{$number};
  39. return "yowsup $number already defined as $d->{NAME}." if( defined($d) && $d->{NAME} ne $name );
  40. $modules{yowsup}{defptr}{$number} = $hash;
  41. addToDevAttrList( $name, "commandPrefix" );
  42. addToDevAttrList( $name, "allowedCommands" );
  43. addToDevAttrList( $name, "acceptFrom" ) if( $number =~ m/\./ );
  44. $hash->{NUMBER} = $number;
  45. }
  46. $hash->{NAME} = $name;
  47. $hash->{NOTIFYDEV} = "global";
  48. if( $init_done ) {
  49. yowsup_Disconnect($hash);
  50. yowsup_Connect($hash);
  51. } elsif( $hash->{STATE} ne "???" ) {
  52. $hash->{STATE} = "Initialized";
  53. }
  54. return undef;
  55. }
  56. sub
  57. yowsup_Notify($$)
  58. {
  59. my ($hash,$dev) = @_;
  60. return if($dev->{NAME} ne "global");
  61. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  62. yowsup_Disconnect($hash);
  63. yowsup_Connect($hash);
  64. }
  65. sub
  66. yowsup_reConnect($)
  67. {
  68. my ($hash) = @_;
  69. my $name = $hash->{NAME};
  70. Log3 $name, 3, "$name: reConnect";
  71. yowsup_Disconnect($hash);
  72. yowsup_Connect($hash);
  73. }
  74. sub
  75. yowsup_Connect($)
  76. {
  77. my ($hash) = @_;
  78. my $name = $hash->{NAME};
  79. return undef if( $hash->{NUMBER} );
  80. return undef if( AttrVal($name, "disable", 0 ) == 1 );
  81. $hash->{PARTIAL} = "";
  82. my ($yowsup_child, $parent);
  83. if( socketpair($yowsup_child, $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) {
  84. $yowsup_child->autoflush(1);
  85. $parent->autoflush(1);
  86. my $pid = fhemFork();
  87. if(!defined($pid)) {
  88. close $parent;
  89. close $yowsup_child;
  90. my $msg = "$name: Cannot fork: $!";
  91. Log 1, $msg;
  92. return $msg;
  93. }
  94. if( $pid ) {
  95. close $parent;
  96. $hash->{STATE} = "Connected";
  97. $hash->{CONNECTS}++;
  98. $hash->{FH} = $yowsup_child;
  99. $hash->{FD} = fileno($yowsup_child);
  100. $hash->{PID} = $pid;
  101. $hash->{WAITING_FOR_LOGIN} = 1;
  102. $selectlist{$name} = $hash;
  103. } else {
  104. close $yowsup_child;
  105. close STDIN;
  106. close STDOUT;
  107. my $fn = $parent->fileno();
  108. open(STDIN, "<&$fn") or die "can't redirect STDIN $!";
  109. open(STDOUT, ">&$fn") or die "can't redirect STDOUT $!";
  110. #select STDIN; $| = 1;
  111. #select STDOUT; $| = 1;
  112. #STDIN->autoflush(1);
  113. STDOUT->autoflush(1);
  114. close $parent;
  115. $ENV{PYTHONUNBUFFERED} = 1;
  116. if( my $home = AttrVal($name, "home", undef ) ) {
  117. $home = $ENV{'PWD'} if( $home eq 'PWD' );
  118. $ENV{'HOME'} = $home;
  119. Log3 $name, 2, "$name: setting \$HOME to $home";
  120. }
  121. my $cmd = AttrVal($name, "cmd", "/opt/local/bin/yowsup-cli demos -c /root/config.yowsup --yowsup" );
  122. Log3 $name, 2, "$name: starting yoswup-cli: $cmd";
  123. exec split( ' ', $cmd ) or Log3 $name, 1, "exec failed";
  124. Log3 $name, 1, "set the cmd attribut to: <path1>/yowsup-cli demos -c <path2>/config.yowsup --yowsup";
  125. POSIX::_exit(0);;
  126. }
  127. } else {
  128. #$hash->{STATE} = "Connected";
  129. Log3 $name, 3, "$name: socketpair failed";
  130. InternalTimer(gettimeofday()+20, "yowsup_Connect", $hash, 0);
  131. }
  132. }
  133. sub
  134. yowsup_Disconnect($)
  135. {
  136. my ($hash) = @_;
  137. my $name = $hash->{NAME};
  138. return undef if( $hash->{NUMBER} );
  139. RemoveInternalTimer($hash);
  140. return if( !$hash->{FD} );
  141. if( $hash->{PID} ) {
  142. yowsup_Write($hash, '/disconnect' );
  143. kill( 9, $hash->{PID} );
  144. waitpid($hash->{PID}, 0);
  145. delete $hash->{PID};
  146. }
  147. close($hash->{FH}) if($hash->{FH});
  148. delete($hash->{FH});
  149. delete($hash->{FD});
  150. delete($selectlist{$name});
  151. $hash->{STATE} = "Disconnected";
  152. Log3 $name, 3, "$name: Disconnected";
  153. $hash->{LAST_DISCONNECT} = FmtDateTime( gettimeofday() );
  154. }
  155. sub
  156. yowsup_Undefine($$)
  157. {
  158. my ($hash, $arg) = @_;
  159. yowsup_Disconnect($hash);
  160. if( $hash->{NUMBER} ) {
  161. delete $modules{yowsup}{defptr}{$hash->{NUMBER}};
  162. } else {
  163. delete $modules{yowsup}{defptr}{yowsup};
  164. }
  165. return undef;
  166. }
  167. sub
  168. yowsup_Shutdown($)
  169. {
  170. my ($hash) = @_;
  171. yowsup_Disconnect($hash);
  172. return undef;
  173. }
  174. sub
  175. yowsup_Set($$@)
  176. {
  177. my ($hash, $name, $cmd, @args) = @_;
  178. my $list = "";
  179. if( $hash->{NUMBER} ) {
  180. my $phash = $modules{yowsup}{defptr}{yowsup};
  181. $list .= "image send" if( $phash->{PID} );
  182. if( $cmd eq 'image' ) {
  183. return "MASTER not connected" if( !$phash->{PID} );
  184. readingsSingleUpdate( $hash, 'sent', 'image: '. join( ' ', @args ), 1 );
  185. my $number = $hash->{NUMBER};
  186. $number =~ s/\./-/;
  187. my $image = shift(@args);
  188. return yowsup_Write( $phash, "/image send $number $image '". join( ' ', @args ) ."'" );
  189. return undef;
  190. } elsif( $cmd eq 'send' ) {
  191. return "MASTER not connected" if( !$phash->{PID} );
  192. readingsSingleUpdate( $hash, 'sent', join( ' ', @args ), 1 );
  193. my $number = $hash->{NUMBER};
  194. $number =~ s/\./-/;
  195. return yowsup_Write( $phash, "/message send $number '". join( ' ', @args ) ."'" );
  196. return undef;
  197. }
  198. } else {
  199. $list .= "image send raw disconnect:noArg " if( $hash->{PID} );
  200. $list .= "reconnect:noArg";
  201. if( $cmd eq 'raw' ) {
  202. return yowsup_Write( $hash, join( ' ', @args ) );
  203. return undef;
  204. } elsif( $cmd eq 'image' ) {
  205. readingsSingleUpdate( $hash, 'sent', 'image: '. join( ' ', @args ), 1 );
  206. my $number = shift(@args);
  207. $number =~ s/\./-/;
  208. my $image = shift(@args);
  209. return yowsup_Write( $hash, "/image send $number $image '". join( ' ', @args ) ."'" );
  210. return undef;
  211. } elsif( $cmd eq 'send' ) {
  212. readingsSingleUpdate( $hash, 'sent', join( ' ', @args ), 1 );
  213. my $number = shift(@args);
  214. $number =~ s/\./-/;
  215. if( $number =~ m/,/ ) {
  216. return yowsup_Write( $hash, "/message broadcast $number '". join( ' ', @args ) ."'" );
  217. } else {
  218. return yowsup_Write( $hash, "/message send $number '". join( ' ', @args ) ."'" );
  219. }
  220. return undef;
  221. } elsif( $cmd eq 'disconnect' ) {
  222. yowsup_Disconnect($hash);
  223. return undef;
  224. } elsif( $cmd eq 'reconnect' ) {
  225. yowsup_Disconnect($hash);
  226. yowsup_Connect($hash);
  227. return undef;
  228. }
  229. }
  230. return "Unknown argument $cmd, choose one of $list";
  231. }
  232. sub
  233. yowsup_Get($$@)
  234. {
  235. my ($hash, $name, $cmd) = @_;
  236. my $list = "devices:noArg";
  237. if( $cmd eq "devices" ) {
  238. return undef;
  239. }
  240. return "Unknown argument $cmd, choose one of $list";
  241. }
  242. sub
  243. yowsup_Parse($$)
  244. {
  245. my ($hash,$data) = @_;
  246. my $name = $hash->{NAME};
  247. Log3 $name, 4, "$name: parse: $data";
  248. $hash->{TIME} = TimeNow();
  249. RemoveInternalTimer($hash);
  250. InternalTimer(gettimeofday()+60*10, "yowsup_reConnect", $hash, 0);
  251. if( $data =~ m/\[offline\]:/ ) {
  252. readingsSingleUpdate( $hash, "state", 'offline', 1 ) if( ReadingsVal($name,'state','' ) ne 'offline' );
  253. if( $hash->{WAITING_FOR_LOGIN} ) {
  254. yowsup_Write( $hash, '/L' );
  255. yowsup_Write( $hash, '/presence available' );
  256. yowsup_Write( $hash, "/presence name '". AttrVal($name, 'nickname', "") ."'" ) if(defined(AttrVal($name, 'nickname', undef)));
  257. #yowsup_Write( $hash, '/ping' );
  258. delete $hash->{WAITING_FOR_LOGIN};
  259. }
  260. } elsif( $data =~ m/\[connected\]:/ ) {
  261. readingsSingleUpdate( $hash, "state", 'connected', 1 ) if( ReadingsVal($name,'state','' ) ne 'connected' );
  262. } elsif( $data =~ m/Auth: Logged in!/ ) {
  263. readingsSingleUpdate( $hash, "state", 'logged in', 1 ) if( ReadingsVal($name,'state','' ) ne 'logged in' );
  264. }
  265. if( $data =~ m/^CHATSTATE:.*State: (\S*).*From: ([\d-]*)/s ) {
  266. my $chatstate = $1;
  267. my $number = $2;
  268. $number =~ s/-/\./;
  269. if( my $chash = $modules{yowsup}{defptr}{$number} ) {
  270. readingsSingleUpdate( $chash, "chatstate", $chatstate, 1 );
  271. }
  272. #} elsif( $data =~ m/\[(.*)@.*\((.*)\)\]:\[(.*)\]\s*(.*)/s ) {
  273. } elsif( $data =~ m/\[(.*)@.*\((.*)\)\]:\[([^\]]*)\]\s*(.*)(\nMessage)/s
  274. || $data =~ m/\[(.*)@.*\((.*)\)\]:\[([^\]]*)\]\s*(.*)/s ) {
  275. my $number = $1;
  276. my $time = $2;
  277. my $id = $3;
  278. my $message = $4;
  279. my $last_sender;
  280. if( $number =~ m/(\d*)\/(\d*)-(\d*)/ ) {
  281. $number = "$2.$3";
  282. $last_sender = $1;
  283. }
  284. $message =~ s/\n$//;
  285. $message =~ s/[\b]*$//;
  286. my $chash = $modules{yowsup}{defptr}{$number};
  287. if( !$chash ) {
  288. my $accept_from = AttrVal($name, "acceptFrom", undef );
  289. if( !$accept_from || ",$accept_from," =~/,$number,/ ) {
  290. my $define = "$number yowsup $number";
  291. my $cmdret = CommandDefine(undef,$define);
  292. if($cmdret) {
  293. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for number '$number': $cmdret";
  294. } else {
  295. #$cmdret = CommandAttr(undef,"$number alias ".$result->{$id}{name});
  296. $cmdret = CommandAttr(undef,"$number room yowsup");
  297. #$cmdret = CommandAttr(undef,"$number IODev $name");
  298. }
  299. $chash = $modules{yowsup}{defptr}{$number};
  300. }
  301. }
  302. if( $chash ) {
  303. readingsBeginUpdate($chash);
  304. if( $last_sender ) {
  305. readingsBulkUpdate( $chash, "chatstate", "received from: $last_sender" );
  306. } else {
  307. readingsBulkUpdate( $chash, "chatstate", "received" );
  308. }
  309. readingsBulkUpdate( $chash, "message", $message );
  310. readingsEndUpdate($chash, 1);
  311. my $cname = $chash->{NAME};
  312. if( my $prefix = AttrVal($cname, "commandPrefix", undef ) ) {
  313. my $cmd;
  314. if( $prefix eq '0' ) {
  315. } elsif( $prefix eq '1' ) {
  316. $cmd = $message;
  317. } elsif( $message =~ m/^$prefix(.*)/ ) {
  318. $cmd = $1;
  319. }
  320. if( $cmd ) {
  321. my $accept_from = AttrVal($cname, "acceptFrom", undef );
  322. if( !$accept_from || $last_sender || ",$accept_from," =~/,$last_sender,/ ) {
  323. Log3 $name, 3, "$cname: received command: $cmd";
  324. $chash->{SNAME} = $cname;
  325. my $ret = AnalyzeCommandChain( $chash, $cmd );
  326. Log3 $name, 4, "$cname: command result: $ret";
  327. my $number = $chash->{NUMBER};
  328. $number =~ s/\./-/;
  329. yowsup_Write( $hash, "/message send $number '$ret'" ) if( $ret );
  330. } else {
  331. Log3 $cname, 3, "$cname: commands: ". $last_sender?$last_sender:$number ." not allowed";
  332. }
  333. }
  334. } else {
  335. Log3 $cname, 3, "$cname: commands not allowed";
  336. }
  337. } else {
  338. Log3 $name, 3, "$name: sender: $number not allowed";
  339. }
  340. }
  341. }
  342. sub
  343. yowsup_Read($)
  344. {
  345. my ($hash) = @_;
  346. my $name = $hash->{NAME};
  347. my $buf;
  348. my $ret = sysread($hash->{FH}, $buf, 65536 );
  349. if(!defined($ret) || $ret <= 0) {
  350. yowsup_Disconnect( $hash );
  351. Log3 $name, 3, "$name: read: error during sysread: $!" if(!defined($ret));
  352. Log3 $name, 3, "$name: read: end of file reached while sysread" if( $ret <= 0);
  353. InternalTimer(gettimeofday()+10, "yowsup_Connect", $hash, 0);
  354. return undef;
  355. }
  356. yowsup_Parse($hash,$buf);
  357. return undef;
  358. my $data = $hash->{PARTIAL};
  359. Log3 $name, 5, "yowsup/RAW: $data/$buf";
  360. $data .= $buf;
  361. $hash->{PARTIAL} = $data;
  362. }
  363. sub
  364. yowsup_Write($$)
  365. {
  366. my ($hash, $data) = @_;
  367. my $name = $hash->{NAME};
  368. return "not connected" if( !$hash->{PID} );
  369. #my $ls = chr(226) . chr(128) . chr(168);
  370. #$data =~ s/\n/$ls/g;
  371. $data =~ s/\n/\r/g;
  372. Log3 $name, 3, "$name: sending $data";
  373. syswrite $hash->{FH}, $data ."\n";
  374. return undef;
  375. }
  376. sub
  377. yowsup_Attr($$$)
  378. {
  379. my ($cmd, $name, $attrName, @params) = @_;
  380. my ($attrVal) = @params;
  381. my $orig = $attrVal;
  382. if($attrName eq "allowedCommands" && $cmd eq "set") {
  383. my $aName = "allowed_$name";
  384. my $exists = ($defs{$aName} ? 1 : 0);
  385. AnalyzeCommand(undef, "defmod $aName allowed");
  386. AnalyzeCommand(undef, "attr $aName validFor $name");
  387. AnalyzeCommand(undef, "attr $aName $attrName ".join(" ",@params));
  388. return "$name: ".($exists ? "modifying":"creating").
  389. " device $aName for attribute $attrName";
  390. } elsif( $attrName eq "disable" ) {
  391. my $hash = $defs{$name};
  392. yowsup_Disconnect($hash);
  393. if( $cmd eq "set" && $attrVal ne "0" ) {
  394. $attrVal = 1;
  395. } else {
  396. $attr{$name}{$attrName} = 0;
  397. yowsup_Connect($hash);
  398. }
  399. } elsif( $attrName eq "cmd" ) {
  400. my $hash = $defs{$name};
  401. if( $cmd eq "set" ) {
  402. $attr{$name}{$attrName} = $attrVal;
  403. } else {
  404. delete $attr{$name}{$attrName};
  405. }
  406. yowsup_Disconnect($hash);
  407. yowsup_Connect($hash);
  408. }
  409. if( $cmd eq "set" ) {
  410. if( !defined($orig) || $orig ne $attrVal ) {
  411. $attr{$name}{$attrName} = $attrVal;
  412. return $attrName ." set to ". $attrVal;
  413. }
  414. }
  415. return;
  416. }
  417. 1;
  418. =pod
  419. =item summary interface to the yowsup librbary (for whatsapp)
  420. =item summary_DE Interface zur yowsup Bibliothek (f&uuml;r WhatsApp)
  421. =begin html
  422. <a name="yowsup"></a>
  423. <h3>yowsup</h3>
  424. <ul>
  425. Module to interface to the yowsup library to send and recive WhatsApp messages.<br><br>
  426. Notes:
  427. <ul>
  428. <li>Probably only works on linux/unix systems.</li>
  429. </ul><br>
  430. <a name="yowsup_Define"></a>
  431. <b>Define</b>
  432. <ul>
  433. <code>define &lt;name&gt; yowsup</code><br>
  434. <br>
  435. Defines a yowsup device.<br><br>
  436. Examples:
  437. <ul>
  438. <code>define WhatsApp yowsup</code><br>
  439. </ul>
  440. </ul><br>
  441. <a name="yowsup_Set"></a>
  442. <b>Set</b>
  443. <ul>
  444. <li>image [&lt;number&gt;] &lt;path&gt; [&lt;text&gt;]<br>
  445. sends an image with optional text. &lt;number&gt; has to be given if sending via master device.</li>
  446. <li>send [&lt;numner&gt;] &lt;text&gt;<br>
  447. sends &lt;text&gt;. &lt;number&gt; has to be given if sending via master device.</li>
  448. </ul><br>
  449. <a name="yowsup_Attr"></a>
  450. <b>Attributes</b>
  451. <ul>
  452. <li>cmd<br>
  453. complette commandline to start the yowsup cli client<br>
  454. eg: attr WhatsApp cmd /opt/local/bin/yowsup-cli demos -c /root/config.yowsup --yowsup</li>
  455. <li>home<br>
  456. set $HOME for the started yowsup process<br>
  457. PWD -> set to $PWD<br>
  458. anything else -> use as $HOME</li>
  459. <li>nickname<br>
  460. nickname that will be send as sender</li>
  461. <li>acceptFrom<br>
  462. comma separated list of contacts (numbers) from which messages will be accepted</li>
  463. <li>commandPrefix<br>
  464. not set -> don't accept commands<br>
  465. 0 -> don't accept commands<br>
  466. 1 -> allow commands, every message is interpreted as a fhem command<br>
  467. anything else -> if the message starts with this prefix then everything after the prefix is taken as the command</li>
  468. <li>allowedCommands<br>
  469. A comma separated list of commands that are allowed from this contact.<br>
  470. If set to an empty list <code>, (i.e. comma only)</code> no commands are accepted.<br>
  471. <b>Note: </b>allowedCommands should work as intended, but no guarantee
  472. can be given that there is no way to circumvent it.</li>
  473. </ul>
  474. </ul>
  475. =end html
  476. =cut