70_Pushover.pm 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812
  1. ###############################################################################
  2. # $Id: 70_Pushover.pm 16358 2018-03-09 09:58:05Z loredo $
  3. # https://pushover.net/api
  4. #
  5. package main;
  6. use HttpUtils;
  7. use utf8;
  8. use Data::Dumper;
  9. use HttpUtils;
  10. use Encode;
  11. # initialize ##################################################################
  12. sub Pushover_Initialize($$) {
  13. my ($hash) = @_;
  14. $hash->{DefFn} = "Pushover_Define";
  15. $hash->{UndefFn} = "Pushover_Undefine";
  16. $hash->{SetFn} = "Pushover_Set";
  17. $hash->{AttrList} =
  18. "disable:0,1 disabledForIntervals do_not_notify:0,1 timestamp:0,1 title sound:pushover,bike,bugle,cashregister,classical,cosmic,falling,gamelan,incoming,intermission,magic,mechanical,pianobar,siren,spacealarm,tugboat,alien,climb,persistent,echo,updown,none device priority:0,1,2,-1,-2 callbackUrl retry expire "
  19. . $readingFnAttributes;
  20. #$hash->{parseParams} = 1; # not possible due to legacy msg command schema
  21. $hash->{'.msgParams'} = { parseParams => 1, };
  22. }
  23. # regular Fn ##################################################################
  24. sub Pushover_Define($$) {
  25. my ( $hash, $def ) = @_;
  26. my @a = split( "[ \t]+", $def );
  27. my $name = shift @a;
  28. my $type = shift @a;
  29. return "Invalid number of arguments: "
  30. . "define <name> Pushover <token> <user> [<infix>]"
  31. if ( int(@a) < 2 );
  32. my ( $token, $user, $infix ) = @a;
  33. return "$user does not seem to be a valid user or group token"
  34. if ( $user !~ /^([a-zA-Z0-9]{30})$/ );
  35. if ( defined($token) && defined($user) ) {
  36. $hash->{APP_TOKEN} = $token;
  37. $hash->{USER_KEY} = $user;
  38. if ( defined($infix) && $infix ne "" ) {
  39. $hash->{fhem}{infix} = $infix;
  40. return "Could not register infix, seems to be existing"
  41. if ( !Pushover_addExtension( $name, "Pushover_CGI", $infix ) );
  42. }
  43. # start Validation Timer
  44. RemoveInternalTimer($hash);
  45. if ( ReadingsVal( $name, "tokenState", "invalid" ) ne "valid"
  46. || ReadingsVal( $name, "userState", "invalid" ) ne "valid"
  47. || $init_done )
  48. {
  49. InternalTimer( gettimeofday() + 5,
  50. "Pushover_ValidateUser", $hash, 0 );
  51. }
  52. else {
  53. InternalTimer( gettimeofday() + 21600,
  54. "Pushover_ValidateUser", $hash, 0 );
  55. }
  56. return undef;
  57. }
  58. else {
  59. return "App or user/group token missing.";
  60. }
  61. return undef;
  62. }
  63. sub Pushover_Undefine($$) {
  64. my ( $hash, $name ) = @_;
  65. if ( defined( $hash->{fhem}{infix} ) ) {
  66. Pushover_removeExtension( $hash->{fhem}{infix} );
  67. }
  68. RemoveInternalTimer($hash);
  69. return undef;
  70. }
  71. sub Pushover_Set($@) {
  72. my ( $hash, $name, $cmd, @args ) = @_;
  73. my ( $a, $h ) = parseParams( join " ", @args );
  74. unless ( $cmd =~ /^(msg|msgCancel|glance)$/i ) {
  75. my $usage = "Unknown argument $cmd, choose one of msg glance";
  76. my $cancelIds;
  77. foreach my $key ( sort keys %{ $hash->{READINGS} } ) {
  78. if ( defined( $hash->{READINGS}{$key}{VAL} )
  79. && $hash->{READINGS}{$key}{VAL} ne ""
  80. && $key =~ /^cbCancelId_(\d+)$/ )
  81. {
  82. $cancelIds .= "," if ($cancelIds);
  83. $cancelIds .= $hash->{READINGS}{$key}{VAL};
  84. }
  85. }
  86. $usage .= " msgCancel:" . $cancelIds if ($cancelIds);
  87. return $usage;
  88. }
  89. return "Unable to send message: Device is disabled"
  90. if ( IsDisabled($name) );
  91. return "Unable to send message: User key is invalid"
  92. if ( ReadingsVal( $name, "userState", "valid" ) eq "invalid" );
  93. return "Unable to send message: App token is invalid"
  94. if ( ReadingsVal( $name, "tokenState", "valid" ) eq "invalid" );
  95. return Pushover_SetMessage2( $hash, $cmd, $a, $h )
  96. if (
  97. $cmd eq 'glance'
  98. || (
  99. $cmd eq 'msg'
  100. && ( join( " ", @args ) !~ m/^(".*"|'.*').*$/
  101. || ( defined($h) && keys %{$h} > 0 ) )
  102. )
  103. );
  104. return Pushover_CancelMessage( $hash, $cmd, $a, $h )
  105. if ( lc($cmd) eq 'msgcancel' );
  106. return Pushover_SetMessage( $hash, @args )
  107. if ( $cmd eq 'msg' );
  108. return undef;
  109. }
  110. # module Fn ####################################################################
  111. sub Pushover_addExtension($$$) {
  112. my ( $name, $func, $link ) = @_;
  113. my $url = "/$link";
  114. return 0
  115. if ( defined( $data{FWEXT}{$url} )
  116. && $data{FWEXT}{$url}{deviceName} ne $name );
  117. Log3 $name, 2,
  118. "Pushover $name: Registering Pushover for webhook URI $url ...";
  119. $data{FWEXT}{$url}{deviceName} = $name;
  120. $data{FWEXT}{$url}{FUNC} = $func;
  121. $data{FWEXT}{$url}{LINK} = $link;
  122. $name->{HASH}{FHEMWEB_URI} = $url;
  123. return 1;
  124. }
  125. sub Pushover_removeExtension($) {
  126. my ($link) = @_;
  127. my $url = "/$link";
  128. my $name = $data{FWEXT}{$url}{deviceName};
  129. Log3 $name, 2,
  130. "Pushover $name: Unregistering Pushover for webhook URI $url...";
  131. delete $data{FWEXT}{$url};
  132. delete $name->{HASH}{FHEMWEB_URI};
  133. }
  134. sub Pushover_CGI() {
  135. my ($request) = @_;
  136. my $hash;
  137. my $name = "";
  138. my $link = "";
  139. my $URI = "";
  140. # data received
  141. if ( $request =~ m,^(/[^/]+?)(?:\&|\?)(.*)?$, ) {
  142. $link = $1;
  143. $URI = $2;
  144. # get device name
  145. $name = $data{FWEXT}{$link}{deviceName} if ( $data{FWEXT}{$link} );
  146. $hash = $defs{$name};
  147. # return error if no such device
  148. return ( "text/plain; charset=utf-8",
  149. "NOK No Pushover device for callback $link" )
  150. unless ($name);
  151. Log3 $name, 4, "Pushover $name callback: link='$link' URI='$URI'";
  152. my $webArgs;
  153. my $receipt = "";
  154. my %revReadings;
  155. # extract values from URI
  156. foreach my $pv ( split( "&", $URI ) ) {
  157. next if ( $pv eq "" );
  158. $pv =~ s/\+/ /g;
  159. $pv =~ s/%([\dA-F][\dA-F])/chr(hex($1))/ige;
  160. my ( $p, $v ) = split( "=", $pv, 2 );
  161. $webArgs->{$p} = $v;
  162. }
  163. if ( defined( $webArgs->{receipt} ) ) {
  164. $receipt = $webArgs->{receipt};
  165. }
  166. elsif ( defined( $webArgs->{FhemCallbackId} ) ) {
  167. $receipt = $webArgs->{FhemCallbackId};
  168. }
  169. else {
  170. return ( "text/plain; charset=utf-8",
  171. "NOK missing argument receipt or FhemCallbackId" );
  172. }
  173. # search for existing receipt
  174. keys %{ $hash->{READINGS} };
  175. while ( my ( $key, $value ) = each %{ $hash->{READINGS} } ) {
  176. $revReadings{ $value->{VAL} } = $1
  177. if ( defined( $value->{VAL} ) && $key =~ /^cb_(\d+)$/ );
  178. }
  179. if ( defined( $revReadings{$receipt} ) ) {
  180. my $rAct = "cbAct_" . $revReadings{$receipt};
  181. my $rAck = "cbAck_" . $revReadings{$receipt};
  182. my $rAckAt = "cbAckAt_" . $revReadings{$receipt};
  183. my $rAckBy = "cbAckBy_" . $revReadings{$receipt};
  184. my $rCancelId = "cbCancelId_" . $revReadings{$receipt};
  185. my $rDev = "cbDev_" . $revReadings{$receipt};
  186. return ( "text/plain; charset=utf-8",
  187. "NOK " . $receipt . ": invalid argument 'acknowledged'" )
  188. if ( !defined( $webArgs->{acknowledged} )
  189. || $webArgs->{acknowledged} ne "1" );
  190. return ( "text/plain; charset=utf-8",
  191. "NOK " . $receipt . ": invalid argument 'acknowledged_by'" )
  192. if ( !defined( $webArgs->{acknowledged_by} )
  193. || $webArgs->{acknowledged_by} ne $hash->{USER_KEY} );
  194. if ( ReadingsVal( $name, $rAck, "1" ) eq "0"
  195. && $revReadings{$receipt} > int( time() ) )
  196. {
  197. delete $hash->{READINGS}{$rCancelId}
  198. if ( defined( $hash->{READINGS}{$rCancelId} ) );
  199. readingsBeginUpdate($hash);
  200. readingsBulkUpdate( $hash, $rAck, "1" );
  201. readingsBulkUpdate( $hash, $rAckBy,
  202. $webArgs->{acknowledged_by} );
  203. if ( defined( $webArgs->{acknowledged_at} )
  204. && $webArgs->{acknowledged_at} ne "" )
  205. {
  206. readingsBulkUpdate( $hash, $rAckAt,
  207. $webArgs->{acknowledged_at} );
  208. }
  209. else {
  210. readingsBulkUpdate( $hash, $rAckAt, int( time() ) );
  211. }
  212. my $redirect = "";
  213. # run FHEM command if desired
  214. if ( ReadingsVal( $name, $rAct, "pushover://" ) !~
  215. /^[\w-]+:\/\/.*$/ )
  216. {
  217. $redirect = "pushover://";
  218. fhem ReadingsVal( $name, $rAct, "" );
  219. readingsBulkUpdate( $hash, $rAct,
  220. "executed: " . ReadingsVal( $name, $rAct, "" ) );
  221. }
  222. # redirect to presented URL
  223. if ( ReadingsVal( $name, $rAct, "none" ) =~ /^[\w-]+:\/\/.*$/ )
  224. {
  225. $redirect = ReadingsVal( $name, $rAct, "" );
  226. }
  227. readingsEndUpdate( $hash, 1 );
  228. return (
  229. "text/html; charset=utf-8",
  230. "<html><head><meta http-equiv=\"refresh\" content=\"0;url="
  231. . $redirect
  232. . "\"></head><body><a href=\""
  233. . $redirect
  234. . "\">Click here to get redirected to your destination"
  235. . "</a></body></html>"
  236. ) if ( $redirect ne "" );
  237. }
  238. else {
  239. Log3 $name, 4,
  240. "Pushover $name callback: " . $receipt . " has expired";
  241. return (
  242. "text/plain; charset=utf-8",
  243. "NOK " . $receipt . " has expired"
  244. );
  245. }
  246. }
  247. else {
  248. Log3 $name, 4,
  249. "Pushover $name callback: unable to find existing receipt "
  250. . $receipt;
  251. return ( "text/plain; charset=utf-8",
  252. "NOK unable to find existing receipt " . $receipt );
  253. }
  254. }
  255. # no data received
  256. else {
  257. Log3 $name, 5,
  258. "Pushover $name callback: received malformed request\n$request";
  259. return ( "text/plain; charset=utf-8", "NOK malformed request" );
  260. }
  261. return ( "text/plain; charset=utf-8", "OK" );
  262. }
  263. sub Pushover_SendCommand($$;$\%) {
  264. my ( $hash, $service, $cmd, $type ) = @_;
  265. my $name = $hash->{NAME};
  266. my $address = "api.pushover.net";
  267. my $port = "443";
  268. my $apiVersion = "1";
  269. my $http_method = "POST";
  270. my $http_noshutdown = ( defined( $attr{$name}{"http-noshutdown"} )
  271. && $attr{$name}{"http-noshutdown"} eq "0" ) ? 0 : 1;
  272. my $timeout;
  273. $cmd = ( defined($cmd) ) ? $cmd : "";
  274. Log3 $name, 5, "Pushover $name: called function Pushover_SendCommand()";
  275. my $http_proto;
  276. if ( $port eq "443" ) {
  277. $http_proto = "https";
  278. }
  279. elsif ( defined( $attr{$name}{https} ) && $attr{$name}{https} eq "1" ) {
  280. $http_proto = "https";
  281. $port = "443" if ( $port eq "80" );
  282. }
  283. else {
  284. $http_proto = "http";
  285. }
  286. my %header = (
  287. Agent => 'FHEM-Pushover/1.0.0',
  288. 'User-Agent' => 'FHEM-Pushover/1.0.0',
  289. Accept => 'application/json;charset=UTF-8',
  290. 'Accept-Charset' => 'UTF-8',
  291. );
  292. my $multipart = 0;
  293. if ( $cmd =~ /^--(.+)\r?\nContent-Disposition:/im ) {
  294. $multipart = 1;
  295. $header{'Content-Type'} = "multipart/form-data; boundary=" . $1;
  296. Log3 $name, 5,
  297. "Pushover $name: Sending as content type " . $header{'Content-Type'};
  298. }
  299. if ( !defined( $type->{USER_KEY} ) ) {
  300. $cmd = Pushover_HttpForm( $cmd, $multipart,
  301. { "user" => $hash->{USER_KEY}, "token" => $hash->{APP_TOKEN} } );
  302. }
  303. else {
  304. Log3 $name, 4,
  305. "Pushover $name: USER_KEY found in device name: " . $type->{USER_KEY};
  306. $cmd = Pushover_HttpForm( $cmd, $multipart,
  307. { "user" => $type->{USER_KEY}, "token" => $hash->{APP_TOKEN} } );
  308. }
  309. $cmd .= "--" if ($multipart);
  310. my $URL;
  311. my $response;
  312. my $return;
  313. if ( !defined($cmd) || $cmd eq "" ) {
  314. Log3 $name, 4, "Pushover $name: REQ $service";
  315. }
  316. else {
  317. $cmd = "?" . $cmd . "&"
  318. if ( $http_method eq "GET" || $http_method eq "" );
  319. Log3 $name, 4, "Pushover $name: REQ $service/" . urlDecode($cmd);
  320. }
  321. $URL =
  322. $http_proto . "://"
  323. . $address . ":"
  324. . $port . "/"
  325. . $apiVersion . "/"
  326. . $service;
  327. $URL .= $cmd if ( $http_method eq "GET" || $http_method eq "" );
  328. if ( defined( $attr{$name}{timeout} )
  329. && $attr{$name}{timeout} =~ /^\d+$/ )
  330. {
  331. $timeout = $attr{$name}{timeout};
  332. }
  333. else {
  334. $timeout = 3;
  335. }
  336. # send request via HTTP-GET method
  337. if ( $http_method eq "GET" || $http_method eq "" || $cmd eq "" ) {
  338. Log3 $name, 5,
  339. "Pushover $name: GET "
  340. . urlDecode($URL)
  341. . " (noshutdown="
  342. . $http_noshutdown . ")";
  343. HttpUtils_NonblockingGet(
  344. {
  345. url => $URL,
  346. timeout => $timeout,
  347. noshutdown => $http_noshutdown,
  348. data => undef,
  349. hash => $hash,
  350. service => $service,
  351. cmd => $cmd,
  352. type => $type,
  353. callback => \&Pushover_ReceiveCommand,
  354. httpversion => "1.1",
  355. loglevel => AttrVal( $name, "httpLoglevel", 4 ),
  356. header => \%header,
  357. # sslargs => {
  358. # SSL_verify_mode => 'SSL_verify_PEER',
  359. # },
  360. }
  361. );
  362. }
  363. # send request via HTTP-POST method
  364. elsif ( $http_method eq "POST" ) {
  365. Log3 $name, 5,
  366. "Pushover $name: GET "
  367. . $URL
  368. . " (POST DATA: "
  369. . urlDecode($cmd)
  370. . ", noshutdown="
  371. . $http_noshutdown . ")";
  372. HttpUtils_NonblockingGet(
  373. {
  374. url => $URL,
  375. timeout => $timeout,
  376. noshutdown => $http_noshutdown,
  377. data => $cmd,
  378. hash => $hash,
  379. service => $service,
  380. cmd => $cmd,
  381. type => $type,
  382. callback => \&Pushover_ReceiveCommand,
  383. httpversion => "1.1",
  384. loglevel => AttrVal( $name, "httpLoglevel", 4 ),
  385. header => \%header,
  386. }
  387. );
  388. }
  389. # other HTTP methods are not supported
  390. else {
  391. Log3 $name, 1,
  392. "Pushover $name: ERROR: HTTP method "
  393. . $http_method
  394. . " is not supported.";
  395. }
  396. return;
  397. }
  398. sub Pushover_ReceiveCommand($$$) {
  399. my ( $param, $err, $data ) = @_;
  400. my $hash = $param->{hash};
  401. my $name = $hash->{NAME};
  402. my $service = $param->{service};
  403. my $cmd = $param->{cmd};
  404. my $state = ReadingsVal( $name, "state", "initialized" );
  405. my $values = $param->{type};
  406. my $return;
  407. Log3 $name, 5,
  408. "Pushover $name: Received HttpUtils callback:\n\nPARAM:\n"
  409. . Dumper($param)
  410. . "\n\nERROR:\n"
  411. . Dumper($err)
  412. . "\n\nDATA:\n"
  413. . Dumper($data);
  414. readingsBeginUpdate($hash);
  415. # service not reachable
  416. if ($err) {
  417. $state = "disconnected";
  418. if ( !defined($cmd) || $cmd eq "" ) {
  419. Log3 $name, 4, "Pushover $name: RCV TIMEOUT $service";
  420. }
  421. else {
  422. Log3 $name, 4,
  423. "Pushover $name: RCV TIMEOUT $service/" . urlDecode($cmd);
  424. }
  425. }
  426. # data received
  427. elsif ($data) {
  428. $state = "connected";
  429. if ( !defined($cmd) || $cmd eq "" ) {
  430. Log3 $name, 4, "Pushover $name: RCV $service";
  431. }
  432. else {
  433. Log3 $name, 4, "Pushover $name: RCV $service/" . urlDecode($cmd);
  434. }
  435. if ( $data ne "" ) {
  436. if ( $data =~ /^{/ || $data =~ /^\[/ ) {
  437. if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
  438. Log3 $name, 5, "Pushover $name: RES $service\n" . $data;
  439. }
  440. else {
  441. Log3 $name, 5,
  442. "Pushover $name: RES $service/"
  443. . urlDecode($cmd) . "\n"
  444. . $data;
  445. }
  446. # Use JSON module if possible
  447. eval {
  448. require JSON;
  449. import JSON qw( decode_json );
  450. };
  451. unless ($@) {
  452. my $json = JSON->new->allow_nonref;
  453. my $obj =
  454. eval { $json->decode( Encode::encode_utf8($data) ) };
  455. $return = $obj unless ($@);
  456. }
  457. }
  458. else {
  459. if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
  460. Log3 $name, 5,
  461. "Pushover $name: RES ERROR $service\n" . $data;
  462. }
  463. else {
  464. Log3 $name, 5,
  465. "Pushover $name: RES ERROR $service/"
  466. . urlDecode($cmd) . "\n"
  467. . $data;
  468. }
  469. return undef;
  470. }
  471. }
  472. $return = Encode::encode_utf8($data) if ( ref($return) ne "HASH" );
  473. #######################
  474. # process return data
  475. #
  476. $values{result} = "ok";
  477. # extract API stats
  478. my $apiLimit = 7500;
  479. my $apiRemaining = 1;
  480. my $apiReset;
  481. if ( $param->{httpheader} =~ m/X-Limit-App-Limit:[\s\t]*(.*)[\s\t\n]*/ )
  482. {
  483. $apiLimit = $1;
  484. readingsBulkUpdateIfChanged( $hash, "apiLimit", $1 ),;
  485. }
  486. if ( $param->{httpheader} =~
  487. m/X-Limit-App-Remaining:[\s\t]*(.*)[\s\t\n]*/ )
  488. {
  489. $apiRemaining = $1;
  490. readingsBulkUpdateIfChanged( $hash, "apiRemaining", $1 );
  491. }
  492. if ( $param->{httpheader} =~ m/X-Limit-App-Reset:[\s\t]*(.*)[\s\t\n]*/ )
  493. {
  494. $apiReset = $1;
  495. readingsBulkUpdateIfChanged( $hash, "apiReset", $1 );
  496. }
  497. # Server error
  498. if ( $param->{code} >= 500 ) {
  499. $state = "error";
  500. $values{result} = "Server Error " . $param->{code};
  501. }
  502. # error handling
  503. elsif (
  504. ( $param->{code} == 200 || $param->{code} >= 400 )
  505. && ( ( ref($return) eq "HASH" && $return->{status} ne "1" )
  506. || ( ref($return) ne "HASH" && $return !~ m/"status":1,/ ) )
  507. )
  508. {
  509. $values{result} =
  510. "Error " . $param->{code} . ": Unspecified error occured";
  511. if ( ref($return) eq "HASH" && defined $return->{errors} ) {
  512. $values{result} =
  513. "Error "
  514. . $param->{code} . ": "
  515. . join( ". ", @{ $return->{errors} } );
  516. }
  517. elsif ( ref($return) ne "HASH" && $return =~ m/"errors":\[(.*)\]/ )
  518. {
  519. $values{result} = "Error " . $param->{code} . ": " . $1;
  520. }
  521. $state = "error";
  522. if ( ref($return) eq "HASH" && defined( $return->{token} ) ) {
  523. $state = "unauthorized";
  524. readingsBulkUpdate( $hash, "tokenState", $return->{token} );
  525. }
  526. elsif ( ref($return) ne "HASH" && $return =~ m/"token":"invalid"/ )
  527. {
  528. $state = "unauthorized";
  529. readingsBulkUpdate( $hash, "tokenState", "invalid" );
  530. }
  531. else {
  532. readingsBulkUpdateIfChanged( $hash, "tokenState", "valid" );
  533. }
  534. if ( ref($return) eq "HASH" && defined( $return->{user} ) ) {
  535. $state = "unauthorized" if ( !defined( $values->{USER_KEY} ) );
  536. readingsBulkUpdate( $hash, "userState", $return->{user} )
  537. if ( !defined( $values->{USER_KEY} ) );
  538. $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } =
  539. "USERKEY "
  540. . $values->{USER_KEY} . " "
  541. . $return->{user} . " - "
  542. . $values{result}
  543. if ( defined( $values->{USER_KEY} ) );
  544. }
  545. elsif ( ref($return) ne "HASH" && $return =~ m/"user":"invalid"/ ) {
  546. $state = "unauthorized" if ( !defined( $values->{USER_KEY} ) );
  547. readingsBulkUpdate( $hash, "userState", "invalid" )
  548. if ( !defined( $values->{USER_KEY} ) );
  549. $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } =
  550. "USERKEY "
  551. . $values->{USER_KEY}
  552. . " invalid - "
  553. . $values{result}
  554. if ( defined( $values->{USER_KEY} ) );
  555. }
  556. else {
  557. readingsBulkUpdateIfChanged( $hash, "userState", "valid" )
  558. if ( !defined( $values->{USER_KEY} ) );
  559. delete $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} }
  560. if (
  561. !defined( $values->{USER_KEY} )
  562. && defined(
  563. $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} }
  564. )
  565. );
  566. }
  567. }
  568. else {
  569. $state = "limited" if ( $apiRemaining < 1 );
  570. readingsBulkUpdateIfChanged( $hash, "tokenState", "valid" )
  571. if ( !defined( $values->{USER_KEY} ) );
  572. readingsBulkUpdateIfChanged( $hash, "userState", "valid" )
  573. if ( !defined( $values->{USER_KEY} ) );
  574. }
  575. # messages.json
  576. if ( $service eq "messages.json" ) {
  577. readingsBulkUpdate( $hash, "lastTitle", $values->{title} );
  578. readingsBulkUpdate( $hash, "lastMessage",
  579. urlDecode( $values->{message} ) );
  580. readingsBulkUpdate( $hash, "lastPriority", $values->{priority} );
  581. readingsBulkUpdate( $hash, "lastAction", $values->{action} )
  582. if ( $values->{action} ne "" );
  583. readingsBulkUpdate( $hash, "lastAction", "-" )
  584. if ( $values->{action} eq "" );
  585. readingsBulkUpdate( $hash, "lastDevice", $values->{device} )
  586. if ( $values->{device} ne "" );
  587. readingsBulkUpdate( $hash, "lastDevice",
  588. ReadingsVal( $name, "devices", "all" ) )
  589. if ( $values->{device} eq "" );
  590. if ( ref($return) eq "HASH" ) {
  591. readingsBulkUpdate( $hash, "lastRequest", $return->{request} )
  592. if ( defined $return->{request} );
  593. if ( $values->{expire} ne "" ) {
  594. readingsBulkUpdate( $hash, "cbTitle_" . $values->{cbNr},
  595. $values->{title} );
  596. readingsBulkUpdate(
  597. $hash,
  598. "cbMsg_" . $values->{cbNr},
  599. urlDecode( $values->{message} )
  600. );
  601. readingsBulkUpdate( $hash, "cbPrio_" . $values->{cbNr},
  602. $values->{priority} );
  603. readingsBulkUpdate( $hash, "cbAck_" . $values->{cbNr},
  604. "0" );
  605. if ( $values->{device} ne "" ) {
  606. readingsBulkUpdate( $hash, "cbDev_" . $values->{cbNr},
  607. $values->{device} );
  608. }
  609. else {
  610. readingsBulkUpdate(
  611. $hash,
  612. "cbDev_" . $values->{cbNr},
  613. ReadingsVal( $name, "devices", "all" )
  614. );
  615. }
  616. if ( defined $return->{receipt} ) {
  617. readingsBulkUpdate( $hash, "cb_" . $values->{cbNr},
  618. $return->{receipt} );
  619. readingsBulkUpdate( $hash,
  620. "cbCancelId_" . $values->{cbNr},
  621. $values->{cancel_id} )
  622. if ( defined( $values->{cancel_id} )
  623. && $values->{cancel_id} ne "" );
  624. }
  625. else {
  626. readingsBulkUpdate( $hash, "cb_" . $values->{cbNr},
  627. $values->{cbNr} );
  628. }
  629. readingsBulkUpdate( $hash, "cbAct_" . $values->{cbNr},
  630. $values->{action} )
  631. if ( $values->{action} ne "" );
  632. }
  633. }
  634. elsif ( $values{expire} ne "" ) {
  635. $values{result} =
  636. "SoftFail: Callback not supported. Please install Perl::JSON";
  637. }
  638. }
  639. # receipts/$receipt/cancel.json
  640. elsif ( $service =~ /^receipts\/(.*)\/cancel.json$/ ) {
  641. my $receipt = $1;
  642. my @delete;
  643. foreach my $key ( %{ $hash->{READINGS} } ) {
  644. if ( $key =~ /^cb_(\d+)$/
  645. && $hash->{READINGS}{$key}{VAL} eq $receipt )
  646. {
  647. my $rAct = "cbAct_" . $1;
  648. my $rAck = "cbAck_" . $1;
  649. my $rAckAt = "cbAckAt_" . $1;
  650. my $rAckBy = "cbAckBy_" . $1;
  651. my $rCancelId = "cbCancelId_" . $1;
  652. if ( $param->{code} == 200 ) {
  653. readingsBulkUpdate( $hash, $rAck, "1" );
  654. readingsBulkUpdate( $hash, $rAckAt, int( time() ) );
  655. readingsBulkUpdate( $hash, $rAckBy, "aborted" );
  656. push @delete, $rCancelId;
  657. }
  658. }
  659. }
  660. # cleanup
  661. foreach (@delete) {
  662. delete $hash->{READINGS}{$_}
  663. if ( defined( $hash->{READINGS}{$_} ) );
  664. }
  665. }
  666. # glances.json
  667. elsif ( $service eq "glances.json" ) {
  668. readingsBulkUpdate( $hash, "lastTitle", $values->{title} );
  669. readingsBulkUpdate( $hash, "lastText",
  670. urlDecode( $values->{text} ) )
  671. if ( $values->{text} ne "" );
  672. readingsBulkUpdate( $hash, "lastSubtext",
  673. urlDecode( $values->{subtext} ) )
  674. if ( $values->{subtext} ne "" );
  675. readingsBulkUpdate( $hash, "lastCount", $values->{count} )
  676. if ( $values->{count} ne "" );
  677. readingsBulkUpdate( $hash, "lastPercent", $values->{percent} )
  678. if ( $values->{percent} ne "" );
  679. readingsBulkUpdate( $hash, "lastDevice", $values->{device} )
  680. if ( $values->{device} ne "" );
  681. readingsBulkUpdate( $hash, "lastDevice",
  682. ReadingsVal( $name, "devices", "all" ) )
  683. if ( $values->{device} eq "" );
  684. if ( ref($return) eq "HASH" ) {
  685. readingsBulkUpdate( $hash, "lastRequest", $return->{request} )
  686. if ( defined $return->{request} );
  687. }
  688. }
  689. # users/validate.json
  690. elsif ( $service eq "users/validate.json" ) {
  691. if ( ref($return) eq "HASH" ) {
  692. my $devices = "-";
  693. my $group = "0";
  694. $devices = join( ",", @{ $return->{devices} } )
  695. if ( defined( $return->{devices} ) );
  696. $group = $return->{group} if ( defined( $return->{group} ) );
  697. readingsBulkUpdateIfChanged( $hash, "devices", $devices );
  698. readingsBulkUpdateIfChanged( $hash, "group", $group );
  699. }
  700. }
  701. readingsBulkUpdate( $hash, "lastResult", $values{result} );
  702. }
  703. # Set reading for availability
  704. #
  705. my $available = 0;
  706. $available = 1
  707. if ( $param->{code} ne "429"
  708. && ( $state eq "connected" || $state eq "error" ) );
  709. readingsBulkUpdateIfChanged( $hash, "available", $available );
  710. # Set reading for state
  711. #
  712. readingsBulkUpdateIfChanged( $hash, "state", $state );
  713. # credentials validation loop
  714. #
  715. my $nextTimer = "none";
  716. # if we could not connect, try again in 5 minutes
  717. if ( $state eq "disconnected" ) {
  718. $nextTimer = gettimeofday() + 300;
  719. }
  720. # re-validate every 6 hours if there was no message sent during
  721. # that time
  722. elsif ( $available eq "1" ) {
  723. $nextTimer = gettimeofday() + 21600;
  724. }
  725. # re-validate after API limit was reset
  726. elsif ( $state eq "limited" || $param->{code} == 429 ) {
  727. $nextTimer =
  728. ReadingsVal( $name, "apiReset", gettimeofday() + 21277 ) + 323;
  729. }
  730. RemoveInternalTimer($hash);
  731. $hash->{VALIDATION_TIMER} = $nextTimer;
  732. InternalTimer( $nextTimer, "Pushover_ValidateUser", $hash, 0 )
  733. if ( $nextTimer ne "none" );
  734. readingsEndUpdate( $hash, 1 );
  735. return;
  736. }
  737. sub Pushover_ValidateUser ($;$) {
  738. my ( $hash, $update ) = @_;
  739. my $name = $hash->{NAME};
  740. my $device = AttrVal( $name, "device", "" );
  741. Log3 $name, 5, "Pushover $name: called function Pushover_ValidateUser()";
  742. RemoveInternalTimer($hash);
  743. if ( AttrVal( $name, "disable", 0 ) == 1 ) {
  744. $hash->{VALIDATION_TIMER} = "disabled";
  745. RemoveInternalTimer($hash);
  746. InternalTimer( gettimeofday() + 900, "Pushover_ValidateUser", $hash,
  747. 0 );
  748. return;
  749. }
  750. elsif ( $device ne "" ) {
  751. return Pushover_SendCommand( $hash, "users/validate.json",
  752. "device=$device" );
  753. }
  754. else {
  755. return Pushover_SendCommand( $hash, "users/validate.json" );
  756. }
  757. }
  758. sub Pushover_SetMessage {
  759. my $hash = shift;
  760. my $name = $hash->{NAME};
  761. my %values = ();
  762. Log3 $name, 5, "Pushover $name: called function Pushover_SetMessage()";
  763. # Set defaults
  764. $values{title} = AttrVal( $hash->{NAME}, "title", "" );
  765. $values{message} = "";
  766. $values{device} = AttrVal( $hash->{NAME}, "device", "" );
  767. $values{priority} = AttrVal( $hash->{NAME}, "priority", 0 );
  768. $values{sound} = AttrVal( $hash->{NAME}, "sound", "" );
  769. $values{retry} = AttrVal( $hash->{NAME}, "retry", "" );
  770. $values{expire} = AttrVal( $hash->{NAME}, "expire", "" );
  771. $values{url_title} = "";
  772. $values{action} = "";
  773. # Split parameters
  774. my $param = join( " ", @_ );
  775. my $argc = 0;
  776. if ( $param =~
  777. /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*(\d+)\s*(\d+)\s*(".*"|'.*')\s*(".*"|'.*')\s*$/s
  778. )
  779. {
  780. $argc = 9;
  781. }
  782. elsif ( $param =~
  783. /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*(\d+)\s*(\d+)\s*$/s
  784. )
  785. {
  786. $argc = 7;
  787. }
  788. elsif ( $param =~
  789. /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*$/s )
  790. {
  791. $argc = 5;
  792. }
  793. elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*$/s ) {
  794. $argc = 2;
  795. }
  796. elsif ( $param =~ /(".*"|'.*')\s*$/s ) {
  797. $argc = 1;
  798. }
  799. Log3 $name, 4, "Pushover $name: Found $argc argument(s)";
  800. if ( $argc > 1 ) {
  801. $values{title} = $1;
  802. $values{message} = $2;
  803. Log3 $name, 4,
  804. "Pushover $name: title=$values{title} message=$values{message}";
  805. if ( $argc > 2 ) {
  806. $values{device} = $3;
  807. $values{priority} = $4;
  808. $values{sound} = $5;
  809. Log3 $name, 4,
  810. "Pushover $name: device=$values{device} priority=$values{priority} sound=$values{sound}";
  811. if ( $argc > 5 ) {
  812. $values{retry} = $6;
  813. $values{expire} = $7;
  814. Log3 $name, 4,
  815. "Pushover $name: retry=$values{retry} expire=$values{expire}";
  816. if ( $argc > 7 ) {
  817. $values{url_title} = $8;
  818. $values{action} = $9;
  819. Log3 $name, 4,
  820. "Pushover $name: url_title=$values{url_title} action=$values{action}";
  821. }
  822. }
  823. }
  824. }
  825. elsif ( $argc == 1 ) {
  826. $values{message} = $1;
  827. Log3 $name, 4, "Pushover $name: message=$values{message}";
  828. }
  829. # Remove quotation marks
  830. $values{title} = $1
  831. if ( $values{title} =~ /^['"](.*)['"]$/s );
  832. $values{message} = $1
  833. if ( $values{message} =~ /^['"](.*)['"]$/s );
  834. $values{device} = $1
  835. if ( $values{device} =~ /^['"](.*)['"]$/s );
  836. $values{priority} = $1
  837. if ( $values{priority} =~ /^['"](.*)['"]$/s );
  838. $values{sound} = $1
  839. if ( $values{sound} =~ /^['"](.*)['"]$/s );
  840. $values{retry} = $1
  841. if ( $values{retry} =~ /^['"](.*)['"]$/s );
  842. $values{expire} = $1
  843. if ( $values{expire} =~ /^['"](.*)['"]$/s );
  844. $values{url_title} = $1
  845. if ( $values{url_title} =~ /^['"](.*)['"]$/s );
  846. $values{action} = $1
  847. if ( $values{action} =~ /^['"](.*)['"]$/s );
  848. return Pushover_SetMessage2( $hash, "msg", undef, \%values );
  849. }
  850. sub Pushover_SetMessage2 ($$$$) {
  851. my ( $hash, $cmd, $a, $h ) = @_;
  852. my $name = $hash->{NAME};
  853. my %values = ();
  854. Log3 $name, 5, "Pushover $name: called function Pushover_SetMessage2()";
  855. # general values
  856. $values{title} =
  857. $h->{title} ? $h->{title} : AttrVal( $hash->{NAME}, "title", undef );
  858. $values{device} =
  859. $h->{device} ? $h->{device} : AttrVal( $hash->{NAME}, "device", undef );
  860. # message
  861. if ( $cmd eq "msg" ) {
  862. if ( defined( $h->{message} ) ) {
  863. $values{message} = $h->{message};
  864. }
  865. elsif ( defined( $h->{msg} ) ) {
  866. $values{message} = $h->{msg};
  867. }
  868. elsif ( defined( $h->{text} ) ) {
  869. $values{message} = $h->{text};
  870. }
  871. else {
  872. $values{message} = join ' ', @$a;
  873. }
  874. return
  875. "Usage: $name msg <text> [ option1=<value> option2='<value with space>' ... ]"
  876. unless ( defined( $values{message} ) && $values{message} ne "" );
  877. $values{priority} =
  878. $h->{priority}
  879. ? $h->{priority}
  880. : AttrVal( $hash->{NAME}, "priority", undef );
  881. return "parameter priority is out of scope"
  882. unless ( !$values{priority} || $values{priority} =~ m/^-?\d+$/ );
  883. return "parameter timestamp is out of scope"
  884. unless ( !$values{timestamp} || $values{timestamp} =~ m/\d+$/ );
  885. $values{retry} =
  886. ( $h->{retry} ? $h->{retry} : AttrVal( $name, "retry", undef ) );
  887. return "parameter retry is out of scope"
  888. unless ( !$values{retry}
  889. || ( $values{retry} =~ m/\d+$/ && $values{retry} >= 30 ) );
  890. $values{expire} =
  891. ( $h->{expire} ? $h->{expire} : AttrVal( $name, "expire", undef ) );
  892. return "parameter retry is out of scope"
  893. unless ( !$values{expire} || $values{expire} =~ m/\d+$/ );
  894. return "priority 2 messages require parameters retry and expire"
  895. if ( $values{priority}
  896. && $values{priority} == 2
  897. && !defined( $values{retry} )
  898. && !defined( $values{expire} ) );
  899. return "priority 2 messages require parameter retry"
  900. if ( $values{priority}
  901. && $values{priority} == 2
  902. && !defined( $values{retry} ) );
  903. return "priority 2 messages require parameter expire"
  904. if ( $values{priority}
  905. && $values{priority} == 2
  906. && !defined( $values{expire} ) );
  907. $values{action} =
  908. $h->{action} ? $h->{action} : ( $h->{url} ? $h->{url} : undef );
  909. $values{url_title} = ( $h->{url_title} ? $h->{url_title} : undef );
  910. return "parameter url_title requires parameter action"
  911. if ( defined( $values{url_title} )
  912. && !defined( $values{action} ) );
  913. return "parameter action requires parameter url_title"
  914. if ( defined( $values{action} )
  915. && !defined( $values{url_title} ) );
  916. return "messages containing a URL require parameter expire"
  917. if ( defined( $values{action} )
  918. && defined( $values{url_title} )
  919. && !defined( $values{expire} ) );
  920. $values{sound} =
  921. $h->{sound} ? $h->{sound} : AttrVal( $hash->{NAME}, "sound", undef );
  922. $values{timestamp} = ( $h->{timestamp} ? $h->{timestamp} : undef );
  923. $values{cancel_id} = $h->{cancel_id}
  924. if ( defined( $h->{cancel_id} )
  925. && $values{priority}
  926. && $values{priority} == 2 );
  927. $values{attachment} = $h->{attachment} ? $h->{attachment} : undef;
  928. }
  929. # glances
  930. if ( $cmd eq "glance" ) {
  931. if ( defined( $h->{text} ) ) {
  932. $values{text} = $h->{text};
  933. }
  934. elsif ( defined( $h->{message} ) ) {
  935. $values{text} = $h->{message};
  936. }
  937. elsif ( defined( $h->{msg} ) ) {
  938. $values{text} = $h->{msg};
  939. }
  940. else {
  941. $values{text} = join ' ', @$a;
  942. }
  943. $values{subtext} = ( defined( $h->{subtext} ) ? $h->{subtext} : undef );
  944. $values{count} = ( defined( $h->{count} ) ? $h->{count} : undef );
  945. return "parameter count is out of scope"
  946. unless ( !$values{count} || $values{count} =~ m/-?\d+$/ );
  947. $values{percent} = ( defined( $h->{percent} ) ? $h->{percent} : undef );
  948. return "parameter percent is out of scope"
  949. unless (
  950. !$values{percent}
  951. || ( $values{percent} =~ m/\d+$/
  952. && $values{percent} >= 0
  953. && $values{percent} <= 100 )
  954. );
  955. return
  956. "Usage: $name glance [ title='<value>' text='<value>' subtext='<value>' count=<value> percent=<value> ]"
  957. unless ( defined( $values{title} )
  958. || ( defined( $values{text} ) && $values{text} ne "" )
  959. || defined( $values{subtext} )
  960. || defined( $values{count} )
  961. || defined( $values{percent} ) );
  962. }
  963. my $callback = (
  964. defined( $attr{$name}{callbackUrl} )
  965. && defined( $hash->{fhem}{infix} )
  966. ? $attr{$name}{callbackUrl}
  967. : undef
  968. );
  969. # check if we got a user or group key as device and use it as
  970. # user-key instead of hash->USER_KEY
  971. if ( $values{device}
  972. && $values{device} =~ /^(([A-Za-z0-9]{30}):)?([A-Za-z0-9,_-]*)(.*)$/ )
  973. {
  974. $values{USER_KEY} = $2 if ( $2 ne "" );
  975. $values{device} = $3;
  976. return $hash->{helper}{FAILED_USERKEYS}{ $values{USER_KEY} }
  977. if ( $values{USER_KEY}
  978. && defined( $hash->{helper}{FAILED_USERKEYS}{ $values{USER_KEY} } )
  979. );
  980. }
  981. # set timestamp if desired
  982. $values{timestamp} = int( time() )
  983. if ( !$values{timestamp}
  984. && 1 == AttrVal( $hash->{NAME}, "timestamp", 0 ) );
  985. # correct priority
  986. if ( defined( $values{priority} ) ) {
  987. $values{priority} = 2 if ( $values{priority} > 2 );
  988. $values{priority} = -2 if ( $values{priority} < -2 );
  989. # callback
  990. if ( $callback && $values{priority} > 1 ) {
  991. Log3 $name, 5,
  992. "Pushover $name: Adding emergency callback URL $callback";
  993. $values{callback} = $callback;
  994. }
  995. }
  996. if ( $values{expire} ) {
  997. $values{cbNr} = round( time(), 0 ) + $values{expire};
  998. my $cbReading = "cb_" . $values{cbNr};
  999. until ( ReadingsVal( $name, $cbReading, "" ) eq "" ) {
  1000. $values{cbNr}++;
  1001. $cbReading = "cb_" . $values{cbNr};
  1002. }
  1003. }
  1004. if ( $values{url_title}
  1005. && $values{action}
  1006. && defined( $values{expire} ) )
  1007. {
  1008. my $url;
  1009. if (
  1010. !$callback
  1011. || ( $values{action} !~ /^http[s]?:\/\/.*$/
  1012. && $values{action} =~ /^[\w-]+:\/\/.*$/ )
  1013. )
  1014. {
  1015. $url = $values{action};
  1016. $values{expire} = undef;
  1017. }
  1018. else {
  1019. $url =
  1020. $callback
  1021. . "?acknowledged=1&acknowledged_by="
  1022. . $hash->{USER_KEY}
  1023. . "&FhemCallbackId="
  1024. . $values{cbNr};
  1025. }
  1026. Log3 $name, 5,
  1027. "Pushover $name: Adding supplementary URL '$values{url_title}' ($url) with "
  1028. . "action '$values{action}' (expires after $values{expire} => "
  1029. . "$values{cbNr})";
  1030. $values{url} = $url;
  1031. }
  1032. # generate body text
  1033. my $body;
  1034. my $multipart = 0;
  1035. $multipart = 1 if ( $values{attachment} );
  1036. if ( defined( $values{message} ) ) {
  1037. if ( $values{message} =~ /^\s*nohtml:\s*(.*)$/i ) {
  1038. Log3 $name, 4,
  1039. "Pushover $name: explicitly ignoring HTML tags in message";
  1040. $values{message} = $1;
  1041. }
  1042. elsif ( $values{message} =~
  1043. m/\<(\/|)[biu]\>|\<(\/|)font(.+)\>|\<(\/|)a(.*)\>|\<br\s?\/?\>/i )
  1044. {
  1045. Log3 $name, 4, "Pushover $name: handling message with HTML content";
  1046. $body = Pushover_HttpForm( $body, $multipart, "html", "1" );
  1047. # replace \n by <br /> but ignore \\n
  1048. $values{message} =~ s/(?<!\\)(\\n)/<br \/>/g;
  1049. }
  1050. }
  1051. if ( defined( $values{attachment} ) ) {
  1052. my $path =
  1053. "file://"
  1054. . AttrVal( $name, "storage", AttrVal( "global", "modpath", "." ) );
  1055. $path .= "/" unless ( $path =~ /\/$/ );
  1056. $values{attachment} = $path . $values{attachment};
  1057. }
  1058. $body = Pushover_HttpForm( $body, $multipart, \%values );
  1059. # cleanup callback readings
  1060. keys %{ $hash->{READINGS} };
  1061. while ( my ( $key, $value ) = each %{ $hash->{READINGS} } ) {
  1062. if ( $key =~ /^cb_(\d+)$/ ) {
  1063. my $rTit = "cbTitle_" . $1;
  1064. my $rMsg = "cbMsg_" . $1;
  1065. my $rPrio = "cbPrio_" . $1;
  1066. my $rAct = "cbAct_" . $1;
  1067. my $rAck = "cbAck_" . $1;
  1068. my $rAckAt = "cbAckAt_" . $1;
  1069. my $rAckBy = "cbAckBy_" . $1;
  1070. my $rCancelId = "cbCancelId_" . $1;
  1071. my $rDev = "cbDev_" . $1;
  1072. Log3 $name, 5,
  1073. "Pushover $name: checking to clean up "
  1074. . $hash->{NAME}
  1075. . " $key: time="
  1076. . $1 . " ack="
  1077. . ReadingsVal( $name, $rAck, "-" )
  1078. . " curTime="
  1079. . int( time() );
  1080. if ( ReadingsVal( $name, $rAck, "0" ) eq "1"
  1081. || $1 <= int( time() ) )
  1082. {
  1083. delete $hash->{READINGS}{$key};
  1084. delete $hash->{READINGS}{$rTit};
  1085. delete $hash->{READINGS}{$rMsg};
  1086. delete $hash->{READINGS}{$rPrio};
  1087. delete $hash->{READINGS}{$rAck};
  1088. delete $hash->{READINGS}{$rDev};
  1089. delete $hash->{READINGS}{$rAct}
  1090. if ( defined( $hash->{READINGS}{$rAct} ) );
  1091. delete $hash->{READINGS}{$rAckAt}
  1092. if ( defined( $hash->{READINGS}{$rAckAt} ) );
  1093. delete $hash->{READINGS}{$rAckBy}
  1094. if ( defined( $hash->{READINGS}{$rAckBy} ) );
  1095. delete $hash->{READINGS}{$rCancelId}
  1096. if ( defined( $hash->{READINGS}{$rCancelId} ) );
  1097. Log3 $name, 4,
  1098. "Pushover $name: cleaned up expired receipt " . $1;
  1099. }
  1100. }
  1101. }
  1102. return Pushover_SendCommand( $hash, "messages.json", $body, %values )
  1103. if ( $cmd eq "msg" );
  1104. return Pushover_SendCommand( $hash, "glances.json", $body, %values )
  1105. if ( $cmd eq "glance" );
  1106. }
  1107. sub Pushover_CancelMessage ($$$$) {
  1108. my ( $hash, $cmd, $cancelIds, $h ) = @_;
  1109. my $name = $hash->{NAME};
  1110. my $success = 0;
  1111. my $return;
  1112. return "Unknown argument, choose one of cancel_id"
  1113. if ( int(@$cancelIds) < 1 || $cancelIds[0] =~ /^(\?|help)$/i );
  1114. Log3 $name, 5, "Pushover $name: called function Pushover_CancelMessage()";
  1115. foreach my $string (@$cancelIds) {
  1116. foreach my $cancelId ( split( ',', $string ) ) {
  1117. foreach my $key ( keys %{ $hash->{READINGS} } ) {
  1118. if ( $key =~ /^cbCancelId_(\d+)$/
  1119. && $hash->{READINGS}{$key}{VAL} eq $cancelId )
  1120. {
  1121. $success = 1;
  1122. my $receipt = $hash->{READINGS}{ "cb_" . $1 }{VAL};
  1123. $return .= " " if ($return);
  1124. $return .=
  1125. Pushover_SendCommand( $hash,
  1126. "receipts/$receipt/cancel.json" )
  1127. if ($receipt);
  1128. }
  1129. }
  1130. }
  1131. }
  1132. return "Invalid cancel_id" unless ($success);
  1133. return $return;
  1134. }
  1135. sub Pushover_HttpForm ($$$;$) {
  1136. my ( $ret, $multipart, $h, $v ) = @_;
  1137. $h = { $h => $v } unless ( ref $h eq "HASH" );
  1138. my $boundary = "--msgsgmnt";
  1139. keys %$h;
  1140. while ( my ( $n, $val ) = each %$h ) {
  1141. next unless ( defined($val) );
  1142. $v = $val;
  1143. # multipart/form-data
  1144. if ($multipart) {
  1145. $ret = "--$boundary"
  1146. unless ( $ret && $ret ne "" );
  1147. if ( $multipart eq "2" || $v =~ /^file:\/\/(.*)/i ) {
  1148. $v = $1 if ( defined($1) );
  1149. next unless ( $v =~ /(\w|[-.])+$/ );
  1150. my $fn = $0;
  1151. my ( $err, @content ) =
  1152. FileRead( { FileName => $v, ForceType => "file" } );
  1153. if ( defined($err) ) {
  1154. Log 5, "Pushover_HttpForm: Unable to read file $v: $err";
  1155. next;
  1156. }
  1157. my $MIMEtype = filename2MIMEType($v);
  1158. $v = join( $/, @content );
  1159. $ret .=
  1160. "\r\nContent-Disposition: form-data; "
  1161. . "name=\"$n\"; "
  1162. . "filename=\"$fn\""
  1163. . "\r\nContent-Type: $MIMEtype";
  1164. }
  1165. else {
  1166. $ret .= "\r\nContent-Disposition: form-data; name=\"$n\"";
  1167. $v =~ s/\\n/\r\n/g;
  1168. }
  1169. $ret .= "\r\n\r\n$v\r\n--$boundary";
  1170. }
  1171. # application/x-www-form-urlencoded
  1172. else {
  1173. $ret = Pushover_HttpUri( $ret, $n, $v );
  1174. }
  1175. }
  1176. return $ret;
  1177. }
  1178. sub Pushover_HttpUri ($$;$) {
  1179. my ( $uri, $h, $v ) = @_;
  1180. $h = { $h => $v } unless ( ref $h eq "HASH" );
  1181. keys %$h;
  1182. while ( my ( $n, $val ) = each %$h ) {
  1183. $v = urlEncode($val);
  1184. # replace any URL-encoded \n with their hex equivalent
  1185. # but ignore \\n
  1186. $v =~ s/(?<!%5c)(%5cn)/%0a/g;
  1187. # replace any URL-encoded \\n by \n
  1188. $v =~ s/%5c%5cn/%5cn/g;
  1189. $uri .= "&" if ($uri);
  1190. $uri .= "$n=$v";
  1191. }
  1192. return $uri;
  1193. }
  1194. 1;
  1195. ###############################################################################
  1196. =pod
  1197. =item device
  1198. =item summary text message push functionality using the Pushover smartphone app
  1199. =item summary_DE Push Funktion f&uuml;r Textnachrichten &uuml;ber die Pushover Smartphone App
  1200. =begin html
  1201. <a name="Pushover"></a>
  1202. <h3>Pushover</h3>
  1203. <ul>
  1204. Pushover is a service to receive instant push notifications on your
  1205. phone or tablet from a variety of sources.<br>
  1206. You need an account to use this module.<br>
  1207. For further information about the service see <a href="https://pushover.net">pushover.net</a>.<br>
  1208. <br>
  1209. Installation of Perl module IO::Socket::SSL is mandatory to use this module (i.e. via 'cpan -i IO::Socket::SSL').<br>
  1210. It is recommended to install Perl-JSON to make use of advanced functions like supplementary URLs.<br>
  1211. <br>
  1212. Discuss the module <a href="http://forum.fhem.de/index.php/topic,16215.0.html">here</a>.<br>
  1213. <br>
  1214. <br>
  1215. <a name="PushoverDefine"></a>
  1216. <b>Define</b>
  1217. <ul>
  1218. <code>define &lt;name&gt; Pushover &lt;token&gt; &lt;user&gt; [&lt;infix&gt;]</code><br>
  1219. <br>
  1220. You have to <a href="https://pushover.net/login">create an account</a> to get the user key.<br>
  1221. And you have to <a href="https://pushover.net/apps/build">create an application</a> to get the API token.<br>
  1222. <br>
  1223. Attribute infix is optional to define FHEMWEB uri name for Pushover API callback function.<br>
  1224. Callback URL may be set using attribute callbackUrl (see below).<br>
  1225. Note: A uri name can only be used once within each FHEM instance!<br>
  1226. <br>
  1227. Example:
  1228. <ul>
  1229. <code>define Pushover1 Pushover 01234 56789</code>
  1230. </ul>
  1231. <ul>
  1232. <code>define Pushover1 Pushover 01234 56789 pushCallback1</code>
  1233. </ul>
  1234. </ul>
  1235. <br>
  1236. <a name="PushoverSet"></a>
  1237. <b>Set</b>
  1238. <ul><b>msg</b><ul>
  1239. <code>set &lt;Pushover_device&gt; msg &lt;text&gt; [&lt;option1&gt;=&lt;value&gt; &lt;option2&gt;="&lt;value with space in it&gt;" ...]</code>
  1240. <br>
  1241. <br>
  1242. The following options may be used to adjust message content and delivery behavior:<br>
  1243. <br>
  1244. <code><b>message</b>&nbsp;&nbsp;&nbsp;</code> - type: text - Your message text. Using this option takes precedence; non-option text content will be discarded.<br>
  1245. <code><b>device</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text - Your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma). May also be set to a specific User or Group Key. To address a specific device for a specific User/Group, use User/Group Key first and add device name separated by colon.<br>
  1246. <code><b>title</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text - Your message's title, otherwise your Pushover API app's name is used.<br>
  1247. <code><b>action</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text - Either a FHEM command to run when user taps link or a <a href="https://pushover.net/api#urls">supplementary URL</a> to show with your message.<br>
  1248. <code><b>url_title</b>&nbsp;</code> - type: text - A title for your FHEM command or supplementary URL, otherwise just the URL is shown.<br>
  1249. <code><b>priority</b>&nbsp;&nbsp;</code> - type: integer - Send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as <a href="https://pushover.net/api#priority">high-priority</a> and bypass the user's quiet hours, or 2 to also require confirmation from the user.<br>
  1250. <code><b>retry</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: integer - Mandatory in combination with message priority &gt;= 2.<br>
  1251. <code><b>expire</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: integer - Mandatory in combination with message priority &gt;= 2.<br>
  1252. <code><b>cancel_id</b>&nbsp;</code> - type: text - Custom ID to immediate expire messages with priority &gt;=2 and disable reoccuring notification.<br>
  1253. <code><b>timestamp</b>&nbsp;</code> - type: integer - A Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by the Pushover servers. Takes precendence over attribute timestamp=1.<br>
  1254. <code><b>sound</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text - The name of one of the <a href="https://pushover.net/api#sounds">sounds</a> supported by device clients to override the user's default sound choice.<br>
  1255. <code><b>attachment</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text - Path to an image file that should be attached to the message. The base path is relative to the FHEM directory and may be overwritten using the storage attribute.<br>
  1256. <br>
  1257. Examples:
  1258. <ul>
  1259. <code>set Pushover1 msg My first Pushover message.</code><br>
  1260. <code>set Pushover1 msg My second Pushover message.\nThis time with two lines.</code><br>
  1261. <code>set Pushover1 msg "Another Pushover message in double quotes."</code><br>
  1262. <code>set Pushover1 msg 'Another Pushover message in single quotes.'</code><br>
  1263. <code>set Pushover1 msg message="Pushover message using explicit option for text content." This part of the text will be ignored.</code><br>
  1264. <code>set Pushover1 msg This is a message with a title. title="This is a subject"</code><br>
  1265. <code>set Pushover1 msg title="This is a subject, too!" This is another message with a title set at the beginning of the command.</code><br>
  1266. <code>set Pushover1 msg This message has an attachment! attachment="demolog/pictures/p1.jpg"</code><br>
  1267. <code>set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security issue in living room.</code><br>
  1268. <code>set Pushover1 msg title=Link Have a look to this website: url_title="Open" action="http://fhem.de/" expire=3600</code><br>
  1269. <code>set Pushover1 msg title=Hint expire=3600 This is a reminder to do something. Action will expire in 1h. url_title="Click here for action" action="set device something"</code><br>
  1270. <code>set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security issue in living room. sound=siren url_title="Click here for action" action="set device something"</code><br>
  1271. </ul>
  1272. <br>
  1273. </ul></ul>
  1274. <br>
  1275. <br>
  1276. <ul><b>msgCancel</b><ul>
  1277. <code>set &lt;Pushover_device&gt; msgCancel &lt;ID&gt;</code>
  1278. <br>
  1279. <br>
  1280. Prematurely stopps reoccuring confirmation request for messages with priority &gt;= 2.<br>
  1281. <br>
  1282. Example:
  1283. <ul>
  1284. <code>set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security Alarm in Living room. sound=siren cancel_id=SecurityAlarm</code><br>
  1285. <code>set Pushover1 msgCancel SecurityAlarm</code>
  1286. </ul>
  1287. </ul></ul>
  1288. <br>
  1289. <br>
  1290. <ul><b>msg</b> <u>(deprecated format)</u><ul>
  1291. <code>set &lt;Pushover_device&gt; msg [title] &lt;msg&gt; [&lt;device&gt; &lt;priority&gt; &lt;sound&gt; [&lt;retry&gt; &lt;expire&gt; [&lt;url_title&gt; &lt;action&gt;]]]</code>
  1292. <br>
  1293. <br>
  1294. Examples:
  1295. <ul>
  1296. <code>set Pushover1 msg 'This is a text.'</code><br>
  1297. <code>set Pushover1 msg 'Title' 'This is a text.'</code><br>
  1298. <code>set Pushover1 msg 'Title' 'This is a text.' '' 0 ''</code><br>
  1299. <code>set Pushover1 msg 'Emergency' 'Security issue in living room.' '' 2 'siren' 30 3600</code><br>
  1300. <code>set Pushover1 msg 'Hint' 'This is a reminder to do something' '' 0 '' 0 3600 'Click here for action' 'set device something'</code><br>
  1301. <code>set Pushover1 msg 'Emergency' 'Security issue in living room.' '' 2 'siren' 30 3600 'Click here for action' 'set device something'</code><br>
  1302. </ul>
  1303. <br>
  1304. Notes:
  1305. <ul>
  1306. <li>For the first and the second example the corresponding default attributes for the missing arguments must be defined for the device (see attributes section)
  1307. </li>
  1308. <li>If device is empty, the message will be sent to all devices.
  1309. </li>
  1310. <li>If device has a User or Group Key, the message will be sent to this recipient instead. Should you wish to address a specific device here, add it at the end separated by colon.
  1311. </li>
  1312. <li>If sound is empty, the default setting in the app will be used.
  1313. </li>
  1314. <li>If priority is higher or equal 2, retry and expire must be defined.
  1315. </li>
  1316. </ul>
  1317. </ul></ul>
  1318. <br>
  1319. <br>
  1320. <ul><b>glance</b><ul>
  1321. <code>set &lt;Pushover_device&gt; glance [&lt;text&gt;] [&lt;option1&gt;=&lt;value&gt; &lt;option2&gt;="&lt;value with space in it&gt;" ...]</code>
  1322. <br>
  1323. <br>
  1324. Update <a href="https://pushover.net/api/glances">Pushover's glances</a> on Apple Watch.<br>
  1325. The following options may be used to adjust message content and delivery behavior:<br>
  1326. <br>
  1327. <code><b>title</b>&nbsp;&nbsp;&nbsp;</code> - type: text(100 characters) - A description of the data being shown, such as "Widgets Sold".<br>
  1328. <code><b>text</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text(100 characters) - The main line of data, used on most screens. Using this option takes precedence; non-option text content will be discarded.<br>
  1329. <code><b>subtext</b>&nbsp;</code> - type: text(100 characters) - A second line of data.<br>
  1330. <code><b>count</b>&nbsp;&nbsp;&nbsp;</code> - type: integer(may be negative) - Shown on smaller screens; useful for simple counts.<br>
  1331. <code><b>percent</b>&nbsp;</code> - type: integer(0-100) - Shown on some screens as a progress bar/circle.<br>
  1332. <code><b>device</b>&nbsp;&nbsp;</code> - type: text - Your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma). May also be set to a specific User or Group Key. To address a specific device for a specific User/Group, use User/Group Key first and add device name separated by colon.<br>
  1333. <br>
  1334. </ul></ul>
  1335. <br>
  1336. <b>Get</b> <ul>N/A</ul><br>
  1337. <a name="PushoverAttr"></a>
  1338. <b>Attributes</b>
  1339. <ul>
  1340. <li>
  1341. <a href="#do_not_notify">do_not_notify</a>
  1342. </li>
  1343. <li>
  1344. <a href="#disabledForIntervals">disabledForIntervals</a>
  1345. </li>
  1346. <li>
  1347. <a href="#readingFnAttributes">readingFnAttributes</a>
  1348. </li>
  1349. <li>
  1350. <a name="PushoverAttrcallbackUrl"></a><code>callbackUrl</code><br>
  1351. Set the callback URL to be used to acknowledge messages with emergency priority or supplementary URLs.
  1352. </li>
  1353. <li><a name="PushoverAttrtimestamp"></a><code>timestamp</code><br>
  1354. Send the unix timestamp with each message.
  1355. </li>
  1356. <li><a name="PushoverAttrtitle"></a><code>title</code><br>
  1357. Will be used as title if title is not specified as an argument.
  1358. </li>
  1359. <li><a name="PushoverAttrdevice"></a><code>device</code><br>
  1360. Will be used for the device name if device is not specified as an argument. If left blank, the message will be sent to all devices.
  1361. </li>
  1362. <li><a name="PushoverAttrpriority"></a><code>priority</code><br>
  1363. Will be used as priority value if priority is not specified as an argument. Valid values are -1 = silent / 0 = normal priority / 1 = high priority
  1364. </li>
  1365. <li><a name="PushoverAttrexpire"></a><code>expire</code><br>
  1366. When message priority is 2, this default value will be used for expire when not provided in the message. Needs to be 30 or higher.
  1367. </li>
  1368. <li><a name="PushoverAttrretry"></a><code>retry</code><br>
  1369. When message priority is 2, this default value will be used for retry when not provided in the message.
  1370. </li>
  1371. <li><a name="PushoverAttrsound"></a><code>sound</code><br>
  1372. Will be used as the default sound if sound argument is missing. If left blank the adjusted sound of the app will be used.
  1373. </li>
  1374. <li><a name="PushoverAttrstorage"></a><code>storage</code><br>
  1375. Will be used as the default path when sending attachments, otherwise global attribute modpath will be used.
  1376. </li>
  1377. </ul>
  1378. <br>
  1379. <a name="PushoverEvents"></a>
  1380. <b>Generated events:</b>
  1381. <ul>
  1382. N/A
  1383. </ul>
  1384. </ul>
  1385. =end html
  1386. =begin html_DE
  1387. <a name="Pushover"></a>
  1388. <h3>Pushover</h3>
  1389. <ul>
  1390. Pushover ist ein Dienst, um Benachrichtigungen von einer vielzahl
  1391. von Quellen auf Deinem Smartphone oder Tablet zu empfangen.<br>
  1392. Du brauchst einen Account um dieses Modul zu verwenden.<br>
  1393. F&uuml;r weitere Informationen &uuml;ber den Dienst besuche <a href="https://pushover.net">pushover.net</a>.<br>
  1394. <br>
  1395. Die Installation des Perl Moduls IO::Socket::SSL ist Voraussetzung zur Nutzung dieses Moduls (z.B. via 'cpan -i IO::Socket::SSL').<br>
  1396. Es wird empfohlen Perl-JSON zu installieren, um erweiterte Funktion wie Supplementary URLs nutzen zu k&ouml;nnen.<br>
  1397. <br>
  1398. Diskutiere das Modul <a href="http://forum.fhem.de/index.php/topic,16215.0.html">hier</a>.<br>
  1399. <br>
  1400. <br>
  1401. <a name="PushoverDefine"></a>
  1402. <b>Define</b>
  1403. <ul>
  1404. <code>define &lt;name&gt; Pushover &lt;token&gt; &lt;user&gt; [&lt;infix&gt;]</code><br>
  1405. <br>
  1406. Du musst einen <a href="https://pushover.net/login">Account erstellen</a>, um den User Key zu bekommen.<br>
  1407. Und du musst <a href="https://pushover.net/apps/build">eine Anwendung erstellen</a>, um einen API APP_TOKEN zu bekommen.<br>
  1408. <br>
  1409. Das Attribut infix ist optional, um einen FHEMWEB uri Namen f&uuml;r die Pushover API Callback Funktion zu definieren.<br>
  1410. Die Callback URL Callback URL kann dann mit dem Attribut callbackUrl gesetzt werden (siehe unten).<br>
  1411. Hinweis: Eine infix uri can innerhalb einer FHEM Instanz nur einmal verwendet werden!<br>
  1412. <br>
  1413. Beispiel:
  1414. <ul>
  1415. <code>define Pushover1 Pushover 01234 56789</code>
  1416. </ul>
  1417. <ul>
  1418. <code>define Pushover1 Pushover 01234 56789 pushCallback1</code>
  1419. </ul>
  1420. </ul>
  1421. <br>
  1422. <a name="PushoverSet"></a>
  1423. <b>Set</b>
  1424. <ul><b>msg</b><ul>
  1425. <code>set &lt;Pushover_device&gt; msg &lt;text&gt; [&lt;option1&gt;=&lt;value&gt; &lt;option2&gt;="&lt;value with space in it&gt;" ...]</code>
  1426. <br>
  1427. <br>
  1428. Die folgenden Optionen k&ouml;nnen genutzt werden, um den Nachrichteninhalt und die Zustellung zu beeinflussen::<br>
  1429. <br>
  1430. <code><b>message</b>&nbsp;&nbsp;&nbsp;</code> - Typ: Text - Dein Nachrichtentext. Die Nutzung dieser Option hat Vorrang; Text au&szlig;erhalb wird verworfen.<br>
  1431. <code><b>device</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Text - Dein selbst vergebener Ger&auml;tename, um die Nachricht direkt an dieses Ger&auml;t zu senden anstatt an alle Ger&auml;te gleichzeitig (mehrere Ger&auml;te k&ouml;nnen mit Komma getrennt angegeben werden). Hier kann auch explizit ein User oder Group Key angegeben werden. Um gezielt ein Ger&auml;t einer/s speziellen User/Group anzusprechen, zuerst den User/Group Key angeben, gefolgt vom Ger&auml;tenamen und einem Doppelpunkt als Trennzeichen.<br>
  1432. <code><b>title</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Text - Dein Nachrichten Titel, andernfalls wird der App Name wie in der Pushover API festgelegt verwendet.<br>
  1433. <code><b>action</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Text - Entweder ein auszuf&uuml;hrendes FHEM Kommando, wenn der Empf&auml;nger den Link anklickt oder eine <a href="https://pushover.net/api#urls">supplementary URL</a>, die mit der Nachricht zusammen angezeigt werden soll.<br>
  1434. <code><b>url_title</b>&nbsp;</code> - Typ: Text - Ein Titel f&uuml;r das FHEM Kommando oder die supplementary URL, andernfalls wird die URL direkt angezeigt.<br>
  1435. <code><b>priority</b>&nbsp;&nbsp;</code> - Typ: Integer - Sende mit -2, um keine/n Benachrichtigung/Alarm zu generieren. Sende mit -1, um immer eine lautlose Benachrichtigung zu senden. Sende mit 1, um die Nachricht mit <a href="https://pushover.net/api#priority">hoher Priorit&auml;t</a> anzuzeigen und die Ruhezeiten des Empf&auml;ngers zu umgehen. Oder sende mit 2, um zus&auml;tzlich eine Best&auml;tigung des Empf&auml;ngers anzufordern.<br>
  1436. <code><b>retry</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Integer - Verpflichtend bei einer Nachrichten Priorit&auml;t &gt;= 2.<br>
  1437. <code><b>expire</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Integer - Verpflichtend bei einer Nachrichten Priorit&auml;t &gt;= 2.<br>
  1438. <code><b>cancel_id</b>&nbsp;</code> - Typ: Text - Benutzerdefinierte ID, um Nachrichten mit einer Priorit&auml;t &gt;= 2 sofort ablaufen zu lassen und die wiederholte Benachrichtigung auszuschalten.<br>
  1439. <code><b>timestamp</b>&nbsp;</code> - Typ: Integer - Ein Unix Zeitstempfel mit Datum und Uhrzeit deiner Nachricht, die dem Empf&auml;nger statt der Uhrzeit des Einganges auf den Pushover Servern angezeigt wird. Hat Vorrang bei gesetztem Attribut timestamp=1.<br>
  1440. <code><b>sound</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Text - Der Name eines vom Empf&auml;ngerger&auml;t unterst&uuml;tzten <a href="https://pushover.net/api#sounds">Klangs</a>, um den vom Empf&auml;nger ausgew&auml;hlten Klang zu &uuml;berschreiben.<br>
  1441. <code><b>attachment</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - Typ: Text - Pfad zu einer Bilddatei, welche an die Nachricht angeh&auml;ngt werden soll. Der Basispfad ist relativ zum FHEM Verzeichnis und kann &uuml;ber das storage Attribut &uuml;berschrieben werden.<br>
  1442. <br>
  1443. Beispiele:
  1444. <ul>
  1445. <code>set Pushover1 msg Meine erste Pushover Nachricht.</code><br>
  1446. <code>set Pushover1 msg Meine zweite Pushover Nachricht.\nDiesmal mit zwei Zeilen.</code><br>
  1447. <code>set Pushover1 msg "Eine andere Pushover Nachricht in doppelten Anf&auml;hrungszeichen."</code><br>
  1448. <code>set Pushover1 msg 'Eine andere Pushover Nachricht in einfachen Anf&auml;hrungszeichen.'</code><br>
  1449. <code>set Pushover1 msg message="Pushover Nachricht, die die explizite Nachrichten Option f&uuml;r den Textinhalt verwendet." Dieser Teil des Textes wird ignoriert.</code><br>
  1450. <code>set Pushover1 msg Dies ist eine Nachricht mit einem Titel. title="Dies ist ein Betreff"</code><br>
  1451. <code>set Pushover1 msg Diese Nachricht hat einen Anhang! attachment="demolog/pictures/p1.jpg"</code><br>
  1452. <code>set Pushover1 msg title="Dies ist auch ein Betreff!" Dies ist eine weitere Nachricht mit einem Titel, der am Anfang des Kommandos gesetzt ist.</code><br>
  1453. <code>set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer.</code><br>
  1454. <code>set Pushover1 msg title=Link Schau dir mal diese Website an: url_title="&Ouml;ffnen" action="http://fhem.de/" expire=3600</code><br>
  1455. <code>set Pushover1 msg title=Hinweis expire=3600 Dies ist eine Erinnerung, um etwas zu tun. Der Link verliert in 1h seine G&uuml;ltigkeit. url_title="Hier klicken, um den Befehl auszuf&uuml;hren" action="set device something"</code><br>
  1456. <code>set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer. sound=siren url_title="Hier klicken, um den Befehl auszuf&uuml;hren" action="set device something"</code><br>
  1457. </ul>
  1458. <br>
  1459. </ul></ul>
  1460. <br>
  1461. <br>
  1462. <ul><b>msgCancel</b><ul>
  1463. <code>set &lt;Pushover_device&gt; msgCancel &lt;ID&gt;</code>
  1464. <br>
  1465. <br>
  1466. Stoppt vorzeitig die wiederkehrende Aufforderung zur Best&auml;tigung bei Nachrichten mit Priorit&auml;t &gt;= 2.<br>
  1467. <br>
  1468. Beispiel:
  1469. <ul>
  1470. <code>set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer. sound=siren cancel_id=SicherheitsAlarm</code><br>
  1471. <code>set Pushover1 msgCancel SicherheitsAlarm</code>
  1472. </ul>
  1473. </ul></ul>
  1474. <br>
  1475. <br>
  1476. <ul><b>msg</b> <u>(veraltetes Format)</u><ul>
  1477. <code>set &lt;Pushover_device&gt; msg [title] &lt;msg&gt; [&lt;device&gt; &lt;priority&gt; &lt;sound&gt; [&lt;retry&gt; &lt;expire&gt; [&lt;url_title&gt; &lt;action&gt;]]]</code>
  1478. <br>
  1479. <br>
  1480. Beispiele:
  1481. <ul>
  1482. <code>set Pushover1 msg 'Dies ist ein Text.'</code><br>
  1483. <code>set Pushover1 msg 'Titel' 'Dies ist ein Text.'</code><br>
  1484. <code>set Pushover1 msg 'Titel' 'Dies ist ein Text.' '' 0 ''</code><br>
  1485. <code>set Pushover1 msg 'Notfall' 'Sicherheitsproblem im Wohnzimmer.' '' 2 'siren' 30 3600</code><br>
  1486. <code>set Pushover1 msg 'Erinnerung' 'Dies ist eine Erinnerung an etwas' '' 0 '' 0 3600 'Hier klicken, um Aktion auszuf&uuml;hren' 'set device irgendwas'</code><br>
  1487. <code>set Pushover1 msg 'Notfall' 'Sicherheitsproblem im Wohnzimmer.' '' 2 'siren' 30 3600 'Hier klicken, um Aktion auszuf&uuml;hren' 'set device something'</code><br>
  1488. </ul>
  1489. <br>
  1490. Anmerkungen:
  1491. <ul>
  1492. <li>Bei der Verwendung der ersten beiden Beispiele m&uuml;ssen die entsprechenden Attribute als Ersatz f&uuml;r die fehlenden Parameter belegt sein (s. Attribute)
  1493. </li>
  1494. <li>Wenn device leer ist, wird die Nachricht an alle Ger&auml;te geschickt.
  1495. </li>
  1496. <li>Wenn device ein User oder Group Key ist, wird die Nachricht stattdessen hierhin verschickt. M&ouml;chte man trotzdem ein dediziertes Device angeben, trennt man den Namen mit einem Doppelpunkt ab.
  1497. </li>
  1498. <li>Wenn sound leer ist, dann wird die Standardeinstellung in der App verwendet.
  1499. </li>
  1500. <li>Wenn die Priorit&auml;t h&ouml;her oder gleich 2 ist m&uuml;ssen retry und expire definiert sein.
  1501. </li>
  1502. </ul>
  1503. </ul></ul>
  1504. <br>
  1505. <br>
  1506. <ul><b>glance</b><ul>
  1507. <code>set &lt;Pushover_device&gt; glance [&lt;text&gt;] [&lt;option1&gt;=&lt;value&gt; &lt;option2&gt;="&lt;value with space in it&gt;" ...]</code>
  1508. <br>
  1509. <br>
  1510. Aktualisiert die <a href="https://pushover.net/api/glances">Pushover glances</a> auf einer Apple Watch.<br>
  1511. Die folgenden Optionen k&ouml;nnen genutzt werden, um den Nachrichteninhalt und die Zustellung zu beeinflussen::<br>
  1512. <br>
  1513. <code><b>title</b>&nbsp;&nbsp;&nbsp;</code> - type: text(100 characters) - Eine Beschreibung der Daten, die angezeigt werden, beispielsweise "Verkaufte Dinge".<br>
  1514. <code><b>text</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: text(100 characters) - Textzeile, die in den meisten Ansichten verwendet wird. Die Nutzung dieser Option hat Vorrang; Text au&szlig;erhalb wird verworfen.<br>
  1515. <code><b>subtext</b>&nbsp;</code> - type: text(100 characters) - Eine zweite Zeile mit Text.<br>
  1516. <code><b>count</b>&nbsp;&nbsp;&nbsp;</code> - type: integer(may be negative) - Wird auf kleineren Ansichten dargestellt; n&uuml;tzlich f&uuml;r einfache Z&auml;hlerst&auml;nde.<br>
  1517. <code><b>percent</b>&nbsp;</code> - type: integer(0-100) - Wird bei einigen Ansichten als Fortschrittsbalken/-kreis angezeigt.<br>
  1518. <code><b>device</b>&nbsp;&nbsp;</code> - Typ: Text - Dein selbst vergebener Ger&auml;tename, um die Nachricht direkt an dieses Ger&auml;t zu senden anstatt an alle Ger&auml;te gleichzeitig (mehrere Ger&auml;te k&ouml;nnen mit Komma getrennt angegeben werden). Hier kann auch explizit ein User oder Group Key angegeben werden. Um gezielt ein Ger&auml;t einer/s speziellen User/Group anzusprechen, zuerst den User/Group Key angeben, gefolgt vom Ger&auml;tenamen und einem Doppelpunkt als Trennzeichen.<br>
  1519. <br>
  1520. </ul></ul>
  1521. <br>
  1522. <b>Get</b> <ul>N/A</ul><br>
  1523. <a name="PushoverAttr"></a>
  1524. <b>Attributes</b>
  1525. <ul>
  1526. <li>
  1527. <a href="#do_not_notify">do_not_notify</a>
  1528. </li>
  1529. <li>
  1530. <a href="#disabledForIntervals">disabledForIntervals</a>
  1531. </li>
  1532. <li>
  1533. <a href="#readingFnAttributes">readingFnAttributes</a>
  1534. </li>
  1535. <li><a name="PushoverAttrcallbackUrl"></a><code>callbackUrl</code><br>
  1536. Setzt die Callback URL, um Nachrichten mit Emergency Priorit&auml;t zu best&auml;tigen.
  1537. </li>
  1538. <li><a name="PushoverAttrtimestamp"></a><code>timestamp</code><br>
  1539. Sende den Unix-Zeitstempel mit jeder Nachricht.
  1540. </li>
  1541. <li><a name="title"></a><code>title</code><br>
  1542. Wird beim Senden als Titel verwendet, sofern dieser nicht als Aufrufargument angegeben wurde.
  1543. </li>
  1544. <li><a name="PushoverAttrdevice"></a><code>device</code><br>
  1545. Wird beim Senden als Ger&auml;tename verwendet, sofern dieser nicht als Aufrufargument angegeben wurde. Kann auch generell entfallen, bzw. leer sein, dann wird an alle Ger&auml;te gesendet.
  1546. </li>
  1547. <li><a name="PushoverAttrpriority"></a><code>priority</code><br>
  1548. Wird beim Senden als Priorit&auml;t verwendet, sofern diese nicht als Aufrufargument angegeben wurde. Zul&auml;ssige Werte sind -1 = leise / 0 = normale Priorit&auml;t / 1 = hohe Priorit&auml;t
  1549. </li>
  1550. <li><a name="PushoverAttrexpire"></a><code>expire</code><br>
  1551. Wenn die Nachrichten Priorit&auml;t 2 ist, wird dieser Wert als Standard f&uuml;r expire verwendet, falls dieser nicht in der Nachricht angegeben wurde. Muss 30 oder h&ouml;her sein.
  1552. </li>
  1553. <li><a name="PushoverAttrretry"></a><code>retry</code><br>
  1554. Wenn die Nachrichten Priorit&auml;t 2 ist, wird dieser Wert als Standard f&uuml;r retry verwendet, falls dieser nicht in der Nachricht angegeben wurde.
  1555. </li>
  1556. <li><a name="PushoverAttrsound"></a><code>sound</code><br>
  1557. Wird beim Senden als Titel verwendet, sofern dieser nicht als Aufrufargument angegeben wurde. Kann auch generell entfallen, dann wird der eingestellte Ton der App verwendet.
  1558. </li>
  1559. <li><a name="PushoverAttrstorage"></a><code>storage</code><br>
  1560. Wird als Standardpfad beim Versand von Anh&auml;ngen verwendet, ansonsten wird das globale Attribut modpath benutzt.
  1561. </li>
  1562. </ul>
  1563. <br>
  1564. <a name="PushoverEvents"></a>
  1565. <b>Generated events:</b>
  1566. <ul>
  1567. N/A
  1568. </ul>
  1569. </ul>
  1570. =end html_DE
  1571. =cut