70_Pushover.pm 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773
  1. ###############################################################################
  2. # $Id: 70_Pushover.pm 15162 2017-10-01 11:25:11Z 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. $cmd .= "&" if ( $cmd ne "" );
  287. $cmd .= "token=" . $hash->{APP_TOKEN};
  288. if ( !defined( $type->{USER_KEY} ) ) {
  289. $cmd .= "&user=" . $hash->{USER_KEY};
  290. }
  291. else {
  292. Log3 $name, 4,
  293. "Pushover $name: USER_KEY found in device name: " . $type->{USER_KEY};
  294. $cmd .= "&user=" . $type->{USER_KEY};
  295. }
  296. my $URL;
  297. my $response;
  298. my $return;
  299. if ( !defined($cmd) || $cmd eq "" ) {
  300. Log3 $name, 4, "Pushover $name: REQ $service";
  301. }
  302. else {
  303. $cmd = "?" . $cmd . "&"
  304. if ( $http_method eq "GET" || $http_method eq "" );
  305. Log3 $name, 4, "Pushover $name: REQ $service/" . urlDecode($cmd);
  306. }
  307. $URL =
  308. $http_proto . "://"
  309. . $address . ":"
  310. . $port . "/"
  311. . $apiVersion . "/"
  312. . $service;
  313. $URL .= $cmd if ( $http_method eq "GET" || $http_method eq "" );
  314. if ( defined( $attr{$name}{timeout} )
  315. && $attr{$name}{timeout} =~ /^\d+$/ )
  316. {
  317. $timeout = $attr{$name}{timeout};
  318. }
  319. else {
  320. $timeout = 3;
  321. }
  322. # send request via HTTP-GET method
  323. if ( $http_method eq "GET" || $http_method eq "" || $cmd eq "" ) {
  324. Log3 $name, 5,
  325. "Pushover $name: GET "
  326. . urlDecode($URL)
  327. . " (noshutdown="
  328. . $http_noshutdown . ")";
  329. HttpUtils_NonblockingGet(
  330. {
  331. url => $URL,
  332. timeout => $timeout,
  333. noshutdown => $http_noshutdown,
  334. data => undef,
  335. hash => $hash,
  336. service => $service,
  337. cmd => $cmd,
  338. type => $type,
  339. callback => \&Pushover_ReceiveCommand,
  340. httpversion => "1.1",
  341. loglevel => AttrVal( $name, "httpLoglevel", 4 ),
  342. header => {
  343. Agent => 'FHEM-Pushover/1.0.0',
  344. 'User-Agent' => 'FHEM-Pushover/1.0.0',
  345. Accept => 'application/json;charset=UTF-8',
  346. 'Accept-Charset' => 'UTF-8',
  347. },
  348. # sslargs => {
  349. # SSL_verify_mode => 'SSL_verify_PEER',
  350. # },
  351. }
  352. );
  353. }
  354. # send request via HTTP-POST method
  355. elsif ( $http_method eq "POST" ) {
  356. Log3 $name, 5,
  357. "Pushover $name: GET "
  358. . $URL
  359. . " (POST DATA: "
  360. . urlDecode($cmd)
  361. . ", noshutdown="
  362. . $http_noshutdown . ")";
  363. HttpUtils_NonblockingGet(
  364. {
  365. url => $URL,
  366. timeout => $timeout,
  367. noshutdown => $http_noshutdown,
  368. data => $cmd,
  369. hash => $hash,
  370. service => $service,
  371. cmd => $cmd,
  372. type => $type,
  373. callback => \&Pushover_ReceiveCommand,
  374. httpversion => "1.1",
  375. loglevel => AttrVal( $name, "httpLoglevel", 4 ),
  376. header => {
  377. Agent => 'FHEM-Pushover/1.0.0',
  378. 'User-Agent' => 'FHEM-Pushover/1.0.0',
  379. Accept => 'application/json;charset=UTF-8',
  380. 'Accept-Charset' => 'UTF-8',
  381. },
  382. }
  383. );
  384. }
  385. # other HTTP methods are not supported
  386. else {
  387. Log3 $name, 1,
  388. "Pushover $name: ERROR: HTTP method "
  389. . $http_method
  390. . " is not supported.";
  391. }
  392. return;
  393. }
  394. sub Pushover_ReceiveCommand($$$) {
  395. my ( $param, $err, $data ) = @_;
  396. my $hash = $param->{hash};
  397. my $name = $hash->{NAME};
  398. my $service = $param->{service};
  399. my $cmd = $param->{cmd};
  400. my $state = ReadingsVal( $name, "state", "initialized" );
  401. my $values = $param->{type};
  402. my $return;
  403. Log3 $name, 5,
  404. "Pushover $name: Received HttpUtils callback:\n\nPARAM:\n"
  405. . Dumper($param)
  406. . "\n\nERROR:\n"
  407. . Dumper($err)
  408. . "\n\nDATA:\n"
  409. . Dumper($data);
  410. readingsBeginUpdate($hash);
  411. # service not reachable
  412. if ($err) {
  413. $state = "disconnected";
  414. if ( !defined($cmd) || $cmd eq "" ) {
  415. Log3 $name, 4, "Pushover $name: RCV TIMEOUT $service";
  416. }
  417. else {
  418. Log3 $name, 4,
  419. "Pushover $name: RCV TIMEOUT $service/" . urlDecode($cmd);
  420. }
  421. }
  422. # data received
  423. elsif ($data) {
  424. $state = "connected";
  425. if ( !defined($cmd) || $cmd eq "" ) {
  426. Log3 $name, 4, "Pushover $name: RCV $service";
  427. }
  428. else {
  429. Log3 $name, 4, "Pushover $name: RCV $service/" . urlDecode($cmd);
  430. }
  431. if ( $data ne "" ) {
  432. if ( $data =~ /^{/ || $data =~ /^\[/ ) {
  433. if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
  434. Log3 $name, 5, "Pushover $name: RES $service\n" . $data;
  435. }
  436. else {
  437. Log3 $name, 5,
  438. "Pushover $name: RES $service/"
  439. . urlDecode($cmd) . "\n"
  440. . $data;
  441. }
  442. # Use JSON module if possible
  443. eval {
  444. require JSON;
  445. import JSON qw( decode_json );
  446. };
  447. unless ($@) {
  448. my $json = JSON->new->allow_nonref;
  449. my $obj =
  450. eval { $json->decode( Encode::encode_utf8($data) ) };
  451. $return = $obj unless ($@);
  452. }
  453. }
  454. else {
  455. if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
  456. Log3 $name, 5,
  457. "Pushover $name: RES ERROR $service\n" . $data;
  458. }
  459. else {
  460. Log3 $name, 5,
  461. "Pushover $name: RES ERROR $service/"
  462. . urlDecode($cmd) . "\n"
  463. . $data;
  464. }
  465. return undef;
  466. }
  467. }
  468. $return = Encode::encode_utf8($data) if ( ref($return) ne "HASH" );
  469. #######################
  470. # process return data
  471. #
  472. $values{result} = "ok";
  473. # extract API stats
  474. my $apiLimit = 7500;
  475. my $apiRemaining = 1;
  476. my $apiReset;
  477. if ( $param->{httpheader} =~ m/X-Limit-App-Limit:[\s\t]*(.*)[\s\t\n]*/ )
  478. {
  479. $apiLimit = $1;
  480. readingsBulkUpdateIfChanged( $hash, "apiLimit", $1 ),;
  481. }
  482. if ( $param->{httpheader} =~
  483. m/X-Limit-App-Remaining:[\s\t]*(.*)[\s\t\n]*/ )
  484. {
  485. $apiRemaining = $1;
  486. readingsBulkUpdateIfChanged( $hash, "apiRemaining", $1 );
  487. }
  488. if ( $param->{httpheader} =~ m/X-Limit-App-Reset:[\s\t]*(.*)[\s\t\n]*/ )
  489. {
  490. $apiReset = $1;
  491. readingsBulkUpdateIfChanged( $hash, "apiReset", $1 );
  492. }
  493. # Server error
  494. if ( $param->{code} >= 500 ) {
  495. $state = "error";
  496. $values{result} = "Server Error " . $param->{code};
  497. }
  498. # error handling
  499. elsif (
  500. ( $param->{code} == 200 || $param->{code} >= 400 )
  501. && ( ( ref($return) eq "HASH" && $return->{status} ne "1" )
  502. || ( ref($return) ne "HASH" && $return !~ m/"status":1,/ ) )
  503. )
  504. {
  505. $values{result} =
  506. "Error " . $param->{code} . ": Unspecified error occured";
  507. if ( ref($return) eq "HASH" && defined $return->{errors} ) {
  508. $values{result} =
  509. "Error "
  510. . $param->{code} . ": "
  511. . join( ". ", @{ $return->{errors} } );
  512. }
  513. elsif ( ref($return) ne "HASH" && $return =~ m/"errors":\[(.*)\]/ )
  514. {
  515. $values{result} = "Error " . $param->{code} . ": " . $1;
  516. }
  517. $state = "error";
  518. if ( ref($return) eq "HASH" && defined( $return->{token} ) ) {
  519. $state = "unauthorized";
  520. readingsBulkUpdate( $hash, "tokenState", $return->{token} );
  521. }
  522. elsif ( ref($return) ne "HASH" && $return =~ m/"token":"invalid"/ )
  523. {
  524. $state = "unauthorized";
  525. readingsBulkUpdate( $hash, "tokenState", "invalid" );
  526. }
  527. else {
  528. readingsBulkUpdateIfChanged( $hash, "tokenState", "valid" );
  529. }
  530. if ( ref($return) eq "HASH" && defined( $return->{user} ) ) {
  531. $state = "unauthorized" if ( !defined( $values->{USER_KEY} ) );
  532. readingsBulkUpdate( $hash, "userState", $return->{user} )
  533. if ( !defined( $values->{USER_KEY} ) );
  534. $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } =
  535. "USERKEY "
  536. . $values->{USER_KEY} . " "
  537. . $return->{user} . " - "
  538. . $values{result}
  539. if ( defined( $values->{USER_KEY} ) );
  540. }
  541. elsif ( ref($return) ne "HASH" && $return =~ m/"user":"invalid"/ ) {
  542. $state = "unauthorized" if ( !defined( $values->{USER_KEY} ) );
  543. readingsBulkUpdate( $hash, "userState", "invalid" )
  544. if ( !defined( $values->{USER_KEY} ) );
  545. $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } =
  546. "USERKEY "
  547. . $values->{USER_KEY}
  548. . " invalid - "
  549. . $values{result}
  550. if ( defined( $values->{USER_KEY} ) );
  551. }
  552. else {
  553. readingsBulkUpdateIfChanged( $hash, "userState", "valid" )
  554. if ( !defined( $values->{USER_KEY} ) );
  555. delete $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} }
  556. if (
  557. !defined( $values->{USER_KEY} )
  558. && defined(
  559. $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} }
  560. )
  561. );
  562. }
  563. }
  564. else {
  565. $state = "limited" if ( $apiRemaining < 1 );
  566. readingsBulkUpdateIfChanged( $hash, "tokenState", "valid" )
  567. if ( !defined( $values->{USER_KEY} ) );
  568. readingsBulkUpdateIfChanged( $hash, "userState", "valid" )
  569. if ( !defined( $values->{USER_KEY} ) );
  570. }
  571. # messages.json
  572. if ( $service eq "messages.json" ) {
  573. readingsBulkUpdate( $hash, "lastTitle", $values->{title} );
  574. readingsBulkUpdate( $hash, "lastMessage",
  575. urlDecode( $values->{message} ) );
  576. readingsBulkUpdate( $hash, "lastPriority", $values->{priority} );
  577. readingsBulkUpdate( $hash, "lastAction", $values->{action} )
  578. if ( $values->{action} ne "" );
  579. readingsBulkUpdate( $hash, "lastAction", "-" )
  580. if ( $values->{action} eq "" );
  581. readingsBulkUpdate( $hash, "lastDevice", $values->{device} )
  582. if ( $values->{device} ne "" );
  583. readingsBulkUpdate( $hash, "lastDevice",
  584. ReadingsVal( $name, "devices", "all" ) )
  585. if ( $values->{device} eq "" );
  586. if ( ref($return) eq "HASH" ) {
  587. readingsBulkUpdate( $hash, "lastRequest", $return->{request} )
  588. if ( defined $return->{request} );
  589. if ( $values->{expire} ne "" ) {
  590. readingsBulkUpdate( $hash, "cbTitle_" . $values->{cbNr},
  591. $values->{title} );
  592. readingsBulkUpdate(
  593. $hash,
  594. "cbMsg_" . $values->{cbNr},
  595. urlDecode( $values->{message} )
  596. );
  597. readingsBulkUpdate( $hash, "cbPrio_" . $values->{cbNr},
  598. $values->{priority} );
  599. readingsBulkUpdate( $hash, "cbAck_" . $values->{cbNr},
  600. "0" );
  601. if ( $values->{device} ne "" ) {
  602. readingsBulkUpdate( $hash, "cbDev_" . $values->{cbNr},
  603. $values->{device} );
  604. }
  605. else {
  606. readingsBulkUpdate(
  607. $hash,
  608. "cbDev_" . $values->{cbNr},
  609. ReadingsVal( $name, "devices", "all" )
  610. );
  611. }
  612. if ( defined $return->{receipt} ) {
  613. readingsBulkUpdate( $hash, "cb_" . $values->{cbNr},
  614. $return->{receipt} );
  615. readingsBulkUpdate( $hash,
  616. "cbCancelId_" . $values->{cbNr},
  617. $values->{cancel_id} )
  618. if ( defined( $values->{cancel_id} )
  619. && $values->{cancel_id} ne "" );
  620. }
  621. else {
  622. readingsBulkUpdate( $hash, "cb_" . $values->{cbNr},
  623. $values->{cbNr} );
  624. }
  625. readingsBulkUpdate( $hash, "cbAct_" . $values->{cbNr},
  626. $values->{action} )
  627. if ( $values->{action} ne "" );
  628. }
  629. }
  630. elsif ( $values{expire} ne "" ) {
  631. $values{result} =
  632. "SoftFail: Callback not supported. Please install Perl::JSON";
  633. }
  634. }
  635. # receipts/$receipt/cancel.json
  636. elsif ( $service =~ /^receipts\/(.*)\/cancel.json$/ ) {
  637. my $receipt = $1;
  638. my @delete;
  639. foreach my $key ( %{ $hash->{READINGS} } ) {
  640. if ( $key =~ /^cb_(\d+)$/
  641. && $hash->{READINGS}{$key}{VAL} eq $receipt )
  642. {
  643. my $rAct = "cbAct_" . $1;
  644. my $rAck = "cbAck_" . $1;
  645. my $rAckAt = "cbAckAt_" . $1;
  646. my $rAckBy = "cbAckBy_" . $1;
  647. my $rCancelId = "cbCancelId_" . $1;
  648. if ( $param->{code} == 200 ) {
  649. readingsBulkUpdate( $hash, $rAck, "1" );
  650. readingsBulkUpdate( $hash, $rAckAt, int( time() ) );
  651. readingsBulkUpdate( $hash, $rAckBy, "aborted" );
  652. push @delete, $rCancelId;
  653. }
  654. }
  655. }
  656. # cleanup
  657. foreach (@delete) {
  658. delete $hash->{READINGS}{$_}
  659. if ( defined( $hash->{READINGS}{$_} ) );
  660. }
  661. }
  662. # glances.json
  663. elsif ( $service eq "glances.json" ) {
  664. readingsBulkUpdate( $hash, "lastTitle", $values->{title} );
  665. readingsBulkUpdate( $hash, "lastText",
  666. urlDecode( $values->{text} ) )
  667. if ( $values->{text} ne "" );
  668. readingsBulkUpdate( $hash, "lastSubtext",
  669. urlDecode( $values->{subtext} ) )
  670. if ( $values->{subtext} ne "" );
  671. readingsBulkUpdate( $hash, "lastCount", $values->{count} )
  672. if ( $values->{count} ne "" );
  673. readingsBulkUpdate( $hash, "lastPercent", $values->{percent} )
  674. if ( $values->{percent} ne "" );
  675. readingsBulkUpdate( $hash, "lastDevice", $values->{device} )
  676. if ( $values->{device} ne "" );
  677. readingsBulkUpdate( $hash, "lastDevice",
  678. ReadingsVal( $name, "devices", "all" ) )
  679. if ( $values->{device} eq "" );
  680. if ( ref($return) eq "HASH" ) {
  681. readingsBulkUpdate( $hash, "lastRequest", $return->{request} )
  682. if ( defined $return->{request} );
  683. }
  684. }
  685. # users/validate.json
  686. elsif ( $service eq "users/validate.json" ) {
  687. if ( ref($return) eq "HASH" ) {
  688. my $devices = "-";
  689. my $group = "0";
  690. $devices = join( ",", @{ $return->{devices} } )
  691. if ( defined( $return->{devices} ) );
  692. $group = $return->{group} if ( defined( $return->{group} ) );
  693. readingsBulkUpdateIfChanged( $hash, "devices", $devices );
  694. readingsBulkUpdateIfChanged( $hash, "group", $group );
  695. }
  696. }
  697. readingsBulkUpdate( $hash, "lastResult", $values{result} );
  698. }
  699. # Set reading for availability
  700. #
  701. my $available = 0;
  702. $available = 1
  703. if ( $param->{code} ne "429"
  704. && ( $state eq "connected" || $state eq "error" ) );
  705. readingsBulkUpdateIfChanged( $hash, "available", $available );
  706. # Set reading for state
  707. #
  708. readingsBulkUpdateIfChanged( $hash, "state", $state );
  709. # credentials validation loop
  710. #
  711. my $nextTimer = "none";
  712. # if we could not connect, try again in 5 minutes
  713. if ( $state eq "disconnected" ) {
  714. $nextTimer = gettimeofday() + 300;
  715. }
  716. # re-validate every 6 hours if there was no message sent during
  717. # that time
  718. elsif ( $available eq "1" ) {
  719. $nextTimer = gettimeofday() + 21600;
  720. }
  721. # re-validate after API limit was reset
  722. elsif ( $state eq "limited" || $param->{code} == 429 ) {
  723. $nextTimer =
  724. ReadingsVal( $name, "apiReset", gettimeofday() + 21277 ) + 323;
  725. }
  726. RemoveInternalTimer($hash);
  727. $hash->{VALIDATION_TIMER} = $nextTimer;
  728. InternalTimer( $nextTimer, "Pushover_ValidateUser", $hash, 0 )
  729. if ( $nextTimer ne "none" );
  730. readingsEndUpdate( $hash, 1 );
  731. return;
  732. }
  733. sub Pushover_ValidateUser ($;$) {
  734. my ( $hash, $update ) = @_;
  735. my $name = $hash->{NAME};
  736. my $device = AttrVal( $name, "device", "" );
  737. Log3 $name, 5, "Pushover $name: called function Pushover_ValidateUser()";
  738. RemoveInternalTimer($hash);
  739. if ( AttrVal( $name, "disable", 0 ) == 1 ) {
  740. $hash->{VALIDATION_TIMER} = "disabled";
  741. RemoveInternalTimer($hash);
  742. InternalTimer( gettimeofday() + 900, "Pushover_ValidateUser", $hash,
  743. 0 );
  744. return;
  745. }
  746. elsif ( $device ne "" ) {
  747. return Pushover_SendCommand( $hash, "users/validate.json",
  748. "device=$device" );
  749. }
  750. else {
  751. return Pushover_SendCommand( $hash, "users/validate.json" );
  752. }
  753. }
  754. sub Pushover_SetMessage {
  755. my $hash = shift;
  756. my $name = $hash->{NAME};
  757. my %values = ();
  758. Log3 $name, 5, "Pushover $name: called function Pushover_SetMessage()";
  759. # Set defaults
  760. $values{title} = AttrVal( $hash->{NAME}, "title", "" );
  761. $values{message} = "";
  762. $values{device} = AttrVal( $hash->{NAME}, "device", "" );
  763. $values{priority} = AttrVal( $hash->{NAME}, "priority", 0 );
  764. $values{sound} = AttrVal( $hash->{NAME}, "sound", "" );
  765. $values{retry} = AttrVal( $hash->{NAME}, "retry", "" );
  766. $values{expire} = AttrVal( $hash->{NAME}, "expire", "" );
  767. $values{url_title} = "";
  768. $values{action} = "";
  769. # Split parameters
  770. my $param = join( " ", @_ );
  771. my $argc = 0;
  772. if ( $param =~
  773. /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*(\d+)\s*(\d+)\s*(".*"|'.*')\s*(".*"|'.*')\s*$/s
  774. )
  775. {
  776. $argc = 9;
  777. }
  778. elsif ( $param =~
  779. /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*(\d+)\s*(\d+)\s*$/s
  780. )
  781. {
  782. $argc = 7;
  783. }
  784. elsif ( $param =~
  785. /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*$/s )
  786. {
  787. $argc = 5;
  788. }
  789. elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*$/s ) {
  790. $argc = 2;
  791. }
  792. elsif ( $param =~ /(".*"|'.*')\s*$/s ) {
  793. $argc = 1;
  794. }
  795. Log3 $name, 4, "Pushover $name: Found $argc argument(s)";
  796. if ( $argc > 1 ) {
  797. $values{title} = $1;
  798. $values{message} = $2;
  799. Log3 $name, 4,
  800. "Pushover $name: title=$values{title} message=$values{message}";
  801. if ( $argc > 2 ) {
  802. $values{device} = $3;
  803. $values{priority} = $4;
  804. $values{sound} = $5;
  805. Log3 $name, 4,
  806. "Pushover $name: device=$values{device} priority=$values{priority} sound=$values{sound}";
  807. if ( $argc > 5 ) {
  808. $values{retry} = $6;
  809. $values{expire} = $7;
  810. Log3 $name, 4,
  811. "Pushover $name: retry=$values{retry} expire=$values{expire}";
  812. if ( $argc > 7 ) {
  813. $values{url_title} = $8;
  814. $values{action} = $9;
  815. Log3 $name, 4,
  816. "Pushover $name: url_title=$values{url_title} action=$values{action}";
  817. }
  818. }
  819. }
  820. }
  821. elsif ( $argc == 1 ) {
  822. $values{message} = $1;
  823. Log3 $name, 4, "Pushover $name: message=$values{message}";
  824. }
  825. # Remove quotation marks
  826. $values{title} = $1
  827. if ( $values{title} =~ /^['"](.*)['"]$/s );
  828. $values{message} = $1
  829. if ( $values{message} =~ /^['"](.*)['"]$/s );
  830. $values{device} = $1
  831. if ( $values{device} =~ /^['"](.*)['"]$/s );
  832. $values{priority} = $1
  833. if ( $values{priority} =~ /^['"](.*)['"]$/s );
  834. $values{sound} = $1
  835. if ( $values{sound} =~ /^['"](.*)['"]$/s );
  836. $values{retry} = $1
  837. if ( $values{retry} =~ /^['"](.*)['"]$/s );
  838. $values{expire} = $1
  839. if ( $values{expire} =~ /^['"](.*)['"]$/s );
  840. $values{url_title} = $1
  841. if ( $values{url_title} =~ /^['"](.*)['"]$/s );
  842. $values{action} = $1
  843. if ( $values{action} =~ /^['"](.*)['"]$/s );
  844. return Pushover_SetMessage2( $hash, "msg", undef, \%values );
  845. }
  846. sub Pushover_SetMessage2 ($$$$) {
  847. my ( $hash, $cmd, $a, $h ) = @_;
  848. my $name = $hash->{NAME};
  849. my %values = ();
  850. Log3 $name, 5, "Pushover $name: called function Pushover_SetMessage2()";
  851. # general values
  852. $values{title} =
  853. $h->{title} ? $h->{title} : AttrVal( $hash->{NAME}, "title", undef );
  854. $values{device} =
  855. $h->{device} ? $h->{device} : AttrVal( $hash->{NAME}, "device", undef );
  856. # message
  857. if ( $cmd eq "msg" ) {
  858. if ( defined( $h->{message} ) ) {
  859. $values{message} = $h->{message};
  860. }
  861. elsif ( defined( $h->{msg} ) ) {
  862. $values{message} = $h->{msg};
  863. }
  864. elsif ( defined( $h->{text} ) ) {
  865. $values{message} = $h->{text};
  866. }
  867. else {
  868. $values{message} = join ' ', @$a;
  869. }
  870. return
  871. "Usage: $name msg <text> [ option1=<value> option2='<value with space>' ... ]"
  872. unless ( defined( $values{message} ) && $values{message} ne "" );
  873. $values{priority} =
  874. $h->{priority}
  875. ? $h->{priority}
  876. : AttrVal( $hash->{NAME}, "priority", undef );
  877. return "parameter priority is out of scope"
  878. unless ( !$values{priority} || $values{priority} =~ m/^-?\d+$/ );
  879. return "parameter timestamp is out of scope"
  880. unless ( !$values{timestamp} || $values{timestamp} =~ m/\d+$/ );
  881. $values{retry} =
  882. ( $h->{retry} ? $h->{retry} : AttrVal( $name, "retry", undef ) );
  883. return "parameter retry is out of scope"
  884. unless ( !$values{retry}
  885. || ( $values{retry} =~ m/\d+$/ && $values{retry} >= 30 ) );
  886. $values{expire} =
  887. ( $h->{expire} ? $h->{expire} : AttrVal( $name, "expire", undef ) );
  888. return "parameter retry is out of scope"
  889. unless ( !$values{expire} || $values{expire} =~ m/\d+$/ );
  890. return "priority 2 messages require parameters retry and expire"
  891. if ( $values{priority}
  892. && $values{priority} == 2
  893. && !defined( $values{retry} )
  894. && !defined( $values{expire} ) );
  895. return "priority 2 messages require parameter retry"
  896. if ( $values{priority}
  897. && $values{priority} == 2
  898. && !defined( $values{retry} ) );
  899. return "priority 2 messages require parameter expire"
  900. if ( $values{priority}
  901. && $values{priority} == 2
  902. && !defined( $values{expire} ) );
  903. $values{action} =
  904. $h->{action} ? $h->{action} : ( $h->{url} ? $h->{url} : undef );
  905. $values{url_title} = ( $h->{url_title} ? $h->{url_title} : undef );
  906. return "parameter url_title requires parameter action"
  907. if ( defined( $values{url_title} )
  908. && !defined( $values{action} ) );
  909. return "parameter action requires parameter url_title"
  910. if ( defined( $values{action} )
  911. && !defined( $values{url_title} ) );
  912. return "messages containing a URL require parameter expire"
  913. if ( defined( $values{action} )
  914. && defined( $values{url_title} )
  915. && !defined( $values{expire} ) );
  916. $values{sound} =
  917. $h->{sound} ? $h->{sound} : AttrVal( $hash->{NAME}, "sound", undef );
  918. $values{timestamp} = ( $h->{timestamp} ? $h->{timestamp} : undef );
  919. $values{cancel_id} = $h->{cancel_id}
  920. if ( defined( $h->{cancel_id} )
  921. && $values{priority}
  922. && $values{priority} == 2 );
  923. }
  924. # glances
  925. if ( $cmd eq "glance" ) {
  926. if ( defined( $h->{text} ) ) {
  927. $values{text} = $h->{text};
  928. }
  929. elsif ( defined( $h->{message} ) ) {
  930. $values{text} = $h->{message};
  931. }
  932. elsif ( defined( $h->{msg} ) ) {
  933. $values{text} = $h->{msg};
  934. }
  935. else {
  936. $values{text} = join ' ', @$a;
  937. }
  938. $values{subtext} = ( defined( $h->{subtext} ) ? $h->{subtext} : undef );
  939. $values{count} = ( defined( $h->{count} ) ? $h->{count} : undef );
  940. return "parameter count is out of scope"
  941. unless ( !$values{count} || $values{count} =~ m/-?\d+$/ );
  942. $values{percent} = ( defined( $h->{percent} ) ? $h->{percent} : undef );
  943. return "parameter percent is out of scope"
  944. unless (
  945. !$values{percent}
  946. || ( $values{percent} =~ m/\d+$/
  947. && $values{percent} >= 0
  948. && $values{percent} <= 100 )
  949. );
  950. return
  951. "Usage: $name glance [ title='<value>' text='<value>' subtext='<value>' count=<value> percent=<value> ]"
  952. unless ( defined( $values{title} )
  953. || ( defined( $values{text} ) && $values{text} ne "" )
  954. || defined( $values{subtext} )
  955. || defined( $values{count} )
  956. || defined( $values{percent} ) );
  957. }
  958. my $callback = (
  959. defined( $attr{$name}{callbackUrl} )
  960. && defined( $hash->{fhem}{infix} )
  961. ? $attr{$name}{callbackUrl}
  962. : undef
  963. );
  964. # check if we got a user or group key as device and use it as
  965. # user-key instead of hash->USER_KEY
  966. if ( $values{device}
  967. && $values{device} =~ /^(([A-Za-z0-9]{30}):)?([A-Za-z0-9,_-]*)(.*)$/ )
  968. {
  969. $values{USER_KEY} = $2 if ( $2 ne "" );
  970. $values{device} = $3;
  971. return $hash->{helper}{FAILED_USERKEYS}{ $values{USER_KEY} }
  972. if ( $values{USER_KEY}
  973. && defined( $hash->{helper}{FAILED_USERKEYS}{ $values{USER_KEY} } )
  974. );
  975. }
  976. my $body;
  977. $body = "title=" . urlEncode( $values{title} )
  978. if ( defined( $values{title} ) );
  979. if ( $values{message} ) {
  980. if ( $values{message} =~ /^\s*nohtml:\s*(.*)$/i ) {
  981. Log3 $name, 4,
  982. "Pushover $name: explicitly ignoring HTML tags in message";
  983. $values{message} = $1;
  984. }
  985. elsif ( $values{message} =~
  986. m/\<(\/|)[biu]\>|\<(\/|)font(.+)\>|\<(\/|)a(.*)\>|\<br\s?\/?\>/i )
  987. {
  988. Log3 $name, 4, "Pushover $name: handling message with HTML content";
  989. $body .= "&html=1";
  990. # replace \n by <br /> but ignore \\n
  991. $values{message} =~ s/(?<!\\)(\\n)/<br \/>/g;
  992. }
  993. # HttpUtil's urlEncode() does not handle \n but would escape %
  994. # so we encode first
  995. $values{message} = urlEncode( $values{message} );
  996. # replace any URL-encoded \n with their hex equivalent but ignore \\n
  997. $values{message} =~ s/(?<!%5c)(%5cn)/%0a/g;
  998. # replace any URL-encoded \\n by \n
  999. $values{message} =~ s/%5c%5cn/%5cn/g;
  1000. $body .= "&message=" . $values{message};
  1001. }
  1002. elsif ( $values{text} ) {
  1003. # HttpUtil's urlEncode() does not handle \n but would escape %
  1004. # so we encode first
  1005. $values{text} = urlEncode( $values{text} );
  1006. # replace any URL-encoded \n with their hex equivalent but ignore \\n
  1007. $values{text} =~ s/(?<!%5c)(%5cn)/%0a/g;
  1008. # replace any URL-encoded \\n by \n
  1009. $values{text} =~ s/%5c%5cn/%5cn/g;
  1010. $body .= "&text=" . $values{text};
  1011. }
  1012. if ( $values{subtext} ) {
  1013. # HttpUtil's urlEncode() does not handle \n but would escape %
  1014. # so we encode first
  1015. $values{subtext} = urlEncode( $values{subtext} );
  1016. # replace any URL-encoded \n with their hex equivalent but ignore \\n
  1017. $values{subtext} =~ s/(?<!%5c)(%5cn)/%0a/g;
  1018. # replace any URL-encoded \\n by \n
  1019. $values{subtext} =~ s/%5c%5cn/%5cn/g;
  1020. $body .= "&subtext=" . $values{subtext};
  1021. }
  1022. if ( defined( $values{count} ) ) {
  1023. $body .= "&count=" . $values{count};
  1024. }
  1025. if ( defined( $values{percent} ) ) {
  1026. $body .= "&percent=" . $values{percent};
  1027. }
  1028. if ( $values{device} ) {
  1029. $body .= "&device=" . $values{device};
  1030. }
  1031. if ( $values{priority} ) {
  1032. $values{priority} = 2 if ( $values{priority} > 2 );
  1033. $values{priority} = -2 if ( $values{priority} < -2 );
  1034. $body .= "&priority=" . $values{priority};
  1035. }
  1036. if ( $values{sound} ) {
  1037. $body .= "&sound=" . $values{sound};
  1038. }
  1039. if ( defined( $values{retry} ) ) {
  1040. $body .= "&retry=" . $values{retry};
  1041. }
  1042. if ( defined( $values{expire} ) ) {
  1043. $body .= "&expire=" . $values{expire};
  1044. $values{cbNr} = round( time(), 0 ) + $values{expire};
  1045. my $cbReading = "cb_" . $values{cbNr};
  1046. until ( ReadingsVal( $name, $cbReading, "" ) eq "" ) {
  1047. $values{cbNr}++;
  1048. $cbReading = "cb_" . $values{cbNr};
  1049. }
  1050. }
  1051. if ( $values{timestamp} ) {
  1052. $body .= "&timestamp=" . $values{timestamp};
  1053. }
  1054. elsif ( 1 == AttrVal( $hash->{NAME}, "timestamp", 0 ) ) {
  1055. $body .= "&timestamp=" . int( time() );
  1056. }
  1057. if ( $callback && $values{priority} && $values{priority} > 1 ) {
  1058. Log3 $name, 5,
  1059. "Pushover $name: Adding emergency callback URL $callback";
  1060. $body .= "&callback=" . $callback;
  1061. }
  1062. if ( $values{url_title}
  1063. && $values{action}
  1064. && defined( $values{expire} ) )
  1065. {
  1066. my $url;
  1067. if (
  1068. !$callback
  1069. || ( $values{action} !~ /^http[s]?:\/\/.*$/
  1070. && $values{action} =~ /^[\w-]+:\/\/.*$/ )
  1071. )
  1072. {
  1073. $url = $values{action};
  1074. $values{expire} = undef;
  1075. }
  1076. else {
  1077. $url =
  1078. $callback
  1079. . "?acknowledged=1&acknowledged_by="
  1080. . $hash->{USER_KEY}
  1081. . "&FhemCallbackId="
  1082. . $values{cbNr};
  1083. }
  1084. Log3 $name, 5,
  1085. "Pushover $name: Adding supplementary URL '$values{url_title}' ($url) with "
  1086. . "action '$values{action}' (expires after $values{expire} => "
  1087. . "$values{cbNr})";
  1088. $body =
  1089. $body
  1090. . "&url_title="
  1091. . urlEncode( $values{url_title} ) . "&url="
  1092. . urlEncode($url);
  1093. }
  1094. # cleanup callback readings
  1095. keys %{ $hash->{READINGS} };
  1096. while ( my ( $key, $value ) = each %{ $hash->{READINGS} } ) {
  1097. if ( $key =~ /^cb_(\d+)$/ ) {
  1098. my $rTit = "cbTitle_" . $1;
  1099. my $rMsg = "cbMsg_" . $1;
  1100. my $rPrio = "cbPrio_" . $1;
  1101. my $rAct = "cbAct_" . $1;
  1102. my $rAck = "cbAck_" . $1;
  1103. my $rAckAt = "cbAckAt_" . $1;
  1104. my $rAckBy = "cbAckBy_" . $1;
  1105. my $rCancelId = "cbCancelId_" . $1;
  1106. my $rDev = "cbDev_" . $1;
  1107. Log3 $name, 5,
  1108. "Pushover $name: checking to clean up "
  1109. . $hash->{NAME}
  1110. . " $key: time="
  1111. . $1 . " ack="
  1112. . ReadingsVal( $name, $rAck, "-" )
  1113. . " curTime="
  1114. . int( time() );
  1115. if ( ReadingsVal( $name, $rAck, "0" ) eq "1"
  1116. || $1 <= int( time() ) )
  1117. {
  1118. delete $hash->{READINGS}{$key};
  1119. delete $hash->{READINGS}{$rTit};
  1120. delete $hash->{READINGS}{$rMsg};
  1121. delete $hash->{READINGS}{$rPrio};
  1122. delete $hash->{READINGS}{$rAck};
  1123. delete $hash->{READINGS}{$rDev};
  1124. delete $hash->{READINGS}{$rAct}
  1125. if ( defined( $hash->{READINGS}{$rAct} ) );
  1126. delete $hash->{READINGS}{$rAckAt}
  1127. if ( defined( $hash->{READINGS}{$rAckAt} ) );
  1128. delete $hash->{READINGS}{$rAckBy}
  1129. if ( defined( $hash->{READINGS}{$rAckBy} ) );
  1130. delete $hash->{READINGS}{$rCancelId}
  1131. if ( defined( $hash->{READINGS}{$rCancelId} ) );
  1132. Log3 $name, 4,
  1133. "Pushover $name: cleaned up expired receipt " . $1;
  1134. }
  1135. }
  1136. }
  1137. return Pushover_SendCommand( $hash, "messages.json", $body, %values )
  1138. if ( $cmd eq "msg" );
  1139. return Pushover_SendCommand( $hash, "glances.json", $body, %values )
  1140. if ( $cmd eq "glance" );
  1141. }
  1142. sub Pushover_CancelMessage ($$$$) {
  1143. my ( $hash, $cmd, $cancelIds, $h ) = @_;
  1144. my $name = $hash->{NAME};
  1145. my $success = 0;
  1146. my $return;
  1147. return "Unknown argument, choose one of cancel_id"
  1148. if ( int(@$cancelIds) < 1 || $cancelIds[0] =~ /^(\?|help)$/i );
  1149. Log3 $name, 5, "Pushover $name: called function Pushover_CancelMessage()";
  1150. foreach my $string (@$cancelIds) {
  1151. foreach my $cancelId ( split( ',', $string ) ) {
  1152. foreach my $key ( keys %{ $hash->{READINGS} } ) {
  1153. if ( $key =~ /^cbCancelId_(\d+)$/
  1154. && $hash->{READINGS}{$key}{VAL} eq $cancelId )
  1155. {
  1156. $success = 1;
  1157. my $receipt = $hash->{READINGS}{ "cb_" . $1 }{VAL};
  1158. $return .= " " if ($return);
  1159. $return .=
  1160. Pushover_SendCommand( $hash,
  1161. "receipts/$receipt/cancel.json" )
  1162. if ($receipt);
  1163. }
  1164. }
  1165. }
  1166. }
  1167. return "Invalid cancel_id" unless ($success);
  1168. return $return;
  1169. }
  1170. 1;
  1171. ###############################################################################
  1172. =pod
  1173. =item device
  1174. =item summary text message push functionality using the Pushover smartphone app
  1175. =item summary_DE Push Funktion f&uuml;r Textnachrichten &uuml;ber die Pushover Smartphone App
  1176. =begin html
  1177. <a name="Pushover"></a>
  1178. <h3>Pushover</h3>
  1179. <ul>
  1180. Pushover is a service to receive instant push notifications on your
  1181. phone or tablet from a variety of sources.<br>
  1182. You need an account to use this module.<br>
  1183. For further information about the service see <a href="https://pushover.net">pushover.net</a>.<br>
  1184. <br>
  1185. Installation of Perl module IO::Socket::SSL is mandatory to use this module (i.e. via 'cpan -i IO::Socket::SSL').<br>
  1186. It is recommended to install Perl-JSON to make use of advanced functions like supplementary URLs.<br>
  1187. <br>
  1188. Discuss the module <a href="http://forum.fhem.de/index.php/topic,16215.0.html">here</a>.<br>
  1189. <br>
  1190. <br>
  1191. <a name="PushoverDefine"></a>
  1192. <b>Define</b>
  1193. <ul>
  1194. <code>define &lt;name&gt; Pushover &lt;token&gt; &lt;user&gt; [&lt;infix&gt;]</code><br>
  1195. <br>
  1196. You have to <a href="https://pushover.net/login">create an account</a> to get the user key.<br>
  1197. And you have to <a href="https://pushover.net/apps/build">create an application</a> to get the API token.<br>
  1198. <br>
  1199. Attribute infix is optional to define FHEMWEB uri name for Pushover API callback function.<br>
  1200. Callback URL may be set using attribute callbackUrl (see below).<br>
  1201. Note: A uri name can only be used once within each FHEM instance!<br>
  1202. <br>
  1203. Example:
  1204. <ul>
  1205. <code>define Pushover1 Pushover 01234 56789</code>
  1206. </ul>
  1207. <ul>
  1208. <code>define Pushover1 Pushover 01234 56789 pushCallback1</code>
  1209. </ul>
  1210. </ul>
  1211. <br>
  1212. <a name="PushoverSet"></a>
  1213. <b>Set</b>
  1214. <ul><b>msg</b><ul>
  1215. <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>
  1216. <br>
  1217. <br>
  1218. The following options may be used to adjust message content and delivery behavior:<br>
  1219. <br>
  1220. <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>
  1221. <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>
  1222. <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>
  1223. <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>
  1224. <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>
  1225. <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>
  1226. <code><b>retry</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: integer - Mandatory in combination with message priority &gt;= 2.<br>
  1227. <code><b>expire</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - type: integer - Mandatory in combination with message priority &gt;= 2.<br>
  1228. <code><b>cancel_id</b>&nbsp;</code> - type: text - Custom ID to immediate expire messages with priority &gt;=2 and disable reoccuring notification.<br>
  1229. <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>
  1230. <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>
  1231. <br>
  1232. Examples:
  1233. <ul>
  1234. <code>set Pushover1 msg My first Pushover message.</code><br>
  1235. <code>set Pushover1 msg My second Pushover message.\nThis time with two lines.</code><br>
  1236. <code>set Pushover1 msg "Another Pushover message in double quotes."</code><br>
  1237. <code>set Pushover1 msg 'Another Pushover message in single quotes.'</code><br>
  1238. <code>set Pushover1 msg message="Pushover message using explicit option for text content." This part of the text will be ignored.</code><br>
  1239. <code>set Pushover1 msg This is a message with a title. title="This is a subject"</code><br>
  1240. <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>
  1241. <code>set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security issue in living room.</code><br>
  1242. <code>set Pushover1 msg title=Link Have a look to this website: url_title="Open" action="http://fhem.de/" expire=3600</code><br>
  1243. <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>
  1244. <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>
  1245. </ul>
  1246. <br>
  1247. </ul></ul>
  1248. <br>
  1249. <br>
  1250. <ul><b>msgCancel</b><ul>
  1251. <code>set &lt;Pushover_device&gt; msgCancel &lt;ID&gt;</code>
  1252. <br>
  1253. <br>
  1254. Prematurely stopps reoccuring confirmation request for messages with priority &gt;= 2.<br>
  1255. <br>
  1256. Example:
  1257. <ul>
  1258. <code>set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security Alarm in Living room. sound=siren cancel_id=SecurityAlarm</code><br>
  1259. <code>set Pushover1 msgCancel SecurityAlarm</code>
  1260. </ul>
  1261. </ul></ul>
  1262. <br>
  1263. <br>
  1264. <ul><b>msg</b> <u>(deprecated format)</u><ul>
  1265. <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>
  1266. <br>
  1267. <br>
  1268. Examples:
  1269. <ul>
  1270. <code>set Pushover1 msg 'This is a text.'</code><br>
  1271. <code>set Pushover1 msg 'Title' 'This is a text.'</code><br>
  1272. <code>set Pushover1 msg 'Title' 'This is a text.' '' 0 ''</code><br>
  1273. <code>set Pushover1 msg 'Emergency' 'Security issue in living room.' '' 2 'siren' 30 3600</code><br>
  1274. <code>set Pushover1 msg 'Hint' 'This is a reminder to do something' '' 0 '' 0 3600 'Click here for action' 'set device something'</code><br>
  1275. <code>set Pushover1 msg 'Emergency' 'Security issue in living room.' '' 2 'siren' 30 3600 'Click here for action' 'set device something'</code><br>
  1276. </ul>
  1277. <br>
  1278. Notes:
  1279. <ul>
  1280. <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)
  1281. </li>
  1282. <li>If device is empty, the message will be sent to all devices.
  1283. </li>
  1284. <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.
  1285. </li>
  1286. <li>If sound is empty, the default setting in the app will be used.
  1287. </li>
  1288. <li>If priority is higher or equal 2, retry and expire must be defined.
  1289. </li>
  1290. </ul>
  1291. </ul></ul>
  1292. <br>
  1293. <br>
  1294. <ul><b>glance</b><ul>
  1295. <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>
  1296. <br>
  1297. <br>
  1298. Update <a href="https://pushover.net/api/glances">Pushover's glances</a> on Apple Watch.<br>
  1299. The following options may be used to adjust message content and delivery behavior:<br>
  1300. <br>
  1301. <code><b>title</b>&nbsp;&nbsp;&nbsp;</code> - type: text(100 characters) - A description of the data being shown, such as "Widgets Sold".<br>
  1302. <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>
  1303. <code><b>subtext</b>&nbsp;</code> - type: text(100 characters) - A second line of data.<br>
  1304. <code><b>count</b>&nbsp;&nbsp;&nbsp;</code> - type: integer(may be negative) - Shown on smaller screens; useful for simple counts.<br>
  1305. <code><b>percent</b>&nbsp;</code> - type: integer(0-100) - Shown on some screens as a progress bar/circle.<br>
  1306. <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>
  1307. <br>
  1308. </ul></ul>
  1309. <br>
  1310. <b>Get</b> <ul>N/A</ul><br>
  1311. <a name="PushoverAttr"></a>
  1312. <b>Attributes</b>
  1313. <ul>
  1314. <li>
  1315. <a href="#do_not_notify">do_not_notify</a>
  1316. </li>
  1317. <li>
  1318. <a href="#disabledForIntervals">disabledForIntervals</a>
  1319. </li>
  1320. <li>
  1321. <a href="#readingFnAttributes">readingFnAttributes</a>
  1322. </li>
  1323. <li>
  1324. <a name="PushoverAttrcallbackUrl"></a><code>callbackUrl</code><br>
  1325. Set the callback URL to be used to acknowledge messages with emergency priority or supplementary URLs.
  1326. </li>
  1327. <li><a name="PushoverAttrtimestamp"></a><code>timestamp</code><br>
  1328. Send the unix timestamp with each message.
  1329. </li>
  1330. <li><a name="PushoverAttrtitle"></a><code>title</code><br>
  1331. Will be used as title if title is not specified as an argument.
  1332. </li>
  1333. <li><a name="PushoverAttrdevice"></a><code>device</code><br>
  1334. 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.
  1335. </li>
  1336. <li><a name="PushoverAttrpriority"></a><code>priority</code><br>
  1337. 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
  1338. </li>
  1339. <li><a name="PushoverAttrexpire"></a><code>expire</code><br>
  1340. 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.
  1341. </li>
  1342. <li><a name="PushoverAttrretry"></a><code>retry</code><br>
  1343. When message priority is 2, this default value will be used for retry when not provided in the message.
  1344. </li>
  1345. <li><a name="PushoverAttrsound"></a><code>sound</code><br>
  1346. Will be used as the default sound if sound argument is missing. If left blank the adjusted sound of the app will be used.
  1347. </li>
  1348. </ul>
  1349. <br>
  1350. <a name="PushoverEvents"></a>
  1351. <b>Generated events:</b>
  1352. <ul>
  1353. N/A
  1354. </ul>
  1355. </ul>
  1356. =end html
  1357. =begin html_DE
  1358. <a name="Pushover"></a>
  1359. <h3>Pushover</h3>
  1360. <ul>
  1361. Pushover ist ein Dienst, um Benachrichtigungen von einer vielzahl
  1362. von Quellen auf Deinem Smartphone oder Tablet zu empfangen.<br>
  1363. Du brauchst einen Account um dieses Modul zu verwenden.<br>
  1364. F&uuml;r weitere Informationen &uuml;ber den Dienst besuche <a href="https://pushover.net">pushover.net</a>.<br>
  1365. <br>
  1366. Die Installation des Perl Moduls IO::Socket::SSL ist Voraussetzung zur Nutzung dieses Moduls (z.B. via 'cpan -i IO::Socket::SSL').<br>
  1367. Es wird empfohlen Perl-JSON zu installieren, um erweiterte Funktion wie Supplementary URLs nutzen zu k&ouml;nnen.<br>
  1368. <br>
  1369. Diskutiere das Modul <a href="http://forum.fhem.de/index.php/topic,16215.0.html">hier</a>.<br>
  1370. <br>
  1371. <br>
  1372. <a name="PushoverDefine"></a>
  1373. <b>Define</b>
  1374. <ul>
  1375. <code>define &lt;name&gt; Pushover &lt;token&gt; &lt;user&gt; [&lt;infix&gt;]</code><br>
  1376. <br>
  1377. Du musst einen <a href="https://pushover.net/login">Account erstellen</a>, um den User Key zu bekommen.<br>
  1378. Und du musst <a href="https://pushover.net/apps/build">eine Anwendung erstellen</a>, um einen API APP_TOKEN zu bekommen.<br>
  1379. <br>
  1380. Das Attribut infix ist optional, um einen FHEMWEB uri Namen f&uuml;r die Pushover API Callback Funktion zu definieren.<br>
  1381. Die Callback URL Callback URL kann dann mit dem Attribut callbackUrl gesetzt werden (siehe unten).<br>
  1382. Hinweis: Eine infix uri can innerhalb einer FHEM Instanz nur einmal verwendet werden!<br>
  1383. <br>
  1384. Beispiel:
  1385. <ul>
  1386. <code>define Pushover1 Pushover 01234 56789</code>
  1387. </ul>
  1388. <ul>
  1389. <code>define Pushover1 Pushover 01234 56789 pushCallback1</code>
  1390. </ul>
  1391. </ul>
  1392. <br>
  1393. <a name="PushoverSet"></a>
  1394. <b>Set</b>
  1395. <ul><b>msg</b><ul>
  1396. <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>
  1397. <br>
  1398. <br>
  1399. Die folgenden Optionen k&ouml;nnen genutzt werden, um den Nachrichteninhalt und die Zustellung zu beeinflussen::<br>
  1400. <br>
  1401. <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>
  1402. <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>
  1403. <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>
  1404. <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>
  1405. <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>
  1406. <code><b>priority</b>&nbsp;&nbsp;</code> - Type: 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>
  1407. <code><b>retry</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code> - Type: Integer - Verpflichtend bei einer Nachrichten Priorit&auml;t &gt;= 2.<br>
  1408. <code><b>expire</b>&nbsp;&nbsp;&nbsp;&nbsp;</code> - Type: Integer - Verpflichtend bei einer Nachrichten Priorit&auml;t &gt;= 2.<br>
  1409. <code><b>cancel_id</b>&nbsp;</code> - type: text - Benutzerdefinierte ID, um Nachrichten mit einer Priorit&auml;t &gt;= 2 sofort ablaufen zu lassen und die wiederholte Benachrichtigung auszuschalten.<br>
  1410. <code><b>timestamp</b>&nbsp;</code> - Type: 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>
  1411. <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>
  1412. <br>
  1413. Beispiele:
  1414. <ul>
  1415. <code>set Pushover1 msg Meine erste Pushover Nachricht.</code><br>
  1416. <code>set Pushover1 msg Meine zweite Pushover Nachricht.\nDiesmal mit zwei Zeilen.</code><br>
  1417. <code>set Pushover1 msg "Eine andere Pushover Nachricht in doppelten Anf&auml;hrungszeichen."</code><br>
  1418. <code>set Pushover1 msg 'Eine andere Pushover Nachricht in einfachen Anf&auml;hrungszeichen.'</code><br>
  1419. <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>
  1420. <code>set Pushover1 msg Dies ist eine Nachricht mit einem Titel. title="Dies ist ein Betreff"</code><br>
  1421. <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>
  1422. <code>set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer.</code><br>
  1423. <code>set Pushover1 msg title=Link Schau dir mal diese Website an: url_title="&Ouml;ffnen" action="http://fhem.de/" expire=3600</code><br>
  1424. <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>
  1425. <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>
  1426. </ul>
  1427. <br>
  1428. </ul></ul>
  1429. <br>
  1430. <br>
  1431. <ul><b>msgCancel</b><ul>
  1432. <code>set &lt;Pushover_device&gt; msgCancel &lt;ID&gt;</code>
  1433. <br>
  1434. <br>
  1435. Stoppt vorzeitig die wiederkehrende Aufforderung zur Best&auml;tigung bei Nachrichten mit Priorit&auml;t &gt;= 2.<br>
  1436. <br>
  1437. Beispiel:
  1438. <ul>
  1439. <code>set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer. sound=siren cancel_id=SicherheitsAlarm</code><br>
  1440. <code>set Pushover1 msgCancel SicherheitsAlarm</code>
  1441. </ul>
  1442. </ul></ul>
  1443. <br>
  1444. <br>
  1445. <ul><b>msg</b> <u>(veraltetes Format)</u><ul>
  1446. <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>
  1447. <br>
  1448. <br>
  1449. Beispiele:
  1450. <ul>
  1451. <code>set Pushover1 msg 'Dies ist ein Text.'</code><br>
  1452. <code>set Pushover1 msg 'Titel' 'Dies ist ein Text.'</code><br>
  1453. <code>set Pushover1 msg 'Titel' 'Dies ist ein Text.' '' 0 ''</code><br>
  1454. <code>set Pushover1 msg 'Notfall' 'Sicherheitsproblem im Wohnzimmer.' '' 2 'siren' 30 3600</code><br>
  1455. <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>
  1456. <code>set Pushover1 msg 'Notfall' 'Sicherheitsproblem im Wohnzimmer.' '' 2 'siren' 30 3600 'Hier klicken, um Aktion auszuf&uuml;hren' 'set device something'</code><br>
  1457. </ul>
  1458. <br>
  1459. Anmerkungen:
  1460. <ul>
  1461. <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)
  1462. </li>
  1463. <li>Wenn device leer ist, wird die Nachricht an alle Ger&auml;te geschickt.
  1464. </li>
  1465. <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.
  1466. </li>
  1467. <li>Wenn sound leer ist, dann wird die Standardeinstellung in der App verwendet.
  1468. </li>
  1469. <li>Wenn die Priorit&auml;t h&ouml;her oder gleich 2 ist m&uuml;ssen retry und expire definiert sein.
  1470. </li>
  1471. </ul>
  1472. </ul></ul>
  1473. <br>
  1474. <br>
  1475. <ul><b>glance</b><ul>
  1476. <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>
  1477. <br>
  1478. <br>
  1479. Aktualisiert die <a href="https://pushover.net/api/glances">Pushover glances</a> auf einer Apple Watch.<br>
  1480. Die folgenden Optionen k&ouml;nnen genutzt werden, um den Nachrichteninhalt und die Zustellung zu beeinflussen::<br>
  1481. <br>
  1482. <code><b>title</b>&nbsp;&nbsp;&nbsp;</code> - type: text(100 characters) - Eine Beschreibung der Daten, die angezeigt werden, beispielsweise "Verkaufte Dinge".<br>
  1483. <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>
  1484. <code><b>subtext</b>&nbsp;</code> - type: text(100 characters) - Eine zweite Zeile mit Text.<br>
  1485. <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>
  1486. <code><b>percent</b>&nbsp;</code> - type: integer(0-100) - Wird bei einigen Ansichten als Fortschrittsbalken/-kreis angezeigt.<br>
  1487. <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>
  1488. <br>
  1489. </ul></ul>
  1490. <br>
  1491. <b>Get</b> <ul>N/A</ul><br>
  1492. <a name="PushoverAttr"></a>
  1493. <b>Attributes</b>
  1494. <ul>
  1495. <li>
  1496. <a href="#do_not_notify">do_not_notify</a>
  1497. </li>
  1498. <li>
  1499. <a href="#disabledForIntervals">disabledForIntervals</a>
  1500. </li>
  1501. <li>
  1502. <a href="#readingFnAttributes">readingFnAttributes</a>
  1503. </li>
  1504. <li><a name="PushoverAttrcallbackUrl"></a><code>callbackUrl</code><br>
  1505. Setzt die Callback URL, um Nachrichten mit Emergency Priorit&auml;t zu best&auml;tigen.
  1506. </li>
  1507. <li><a name="PushoverAttrtimestamp"></a><code>timestamp</code><br>
  1508. Sende den Unix-Zeitstempel mit jeder Nachricht.
  1509. </li>
  1510. <li><a name="title"></a><code>title</code><br>
  1511. Wird beim Senden als Titel verwendet, sofern dieser nicht als Aufrufargument angegeben wurde.
  1512. </li>
  1513. <li><a name="PushoverAttrdevice"></a><code>device</code><br>
  1514. 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.
  1515. </li>
  1516. <li><a name="PushoverAttrpriority"></a><code>priority</code><br>
  1517. 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
  1518. </li>
  1519. <li><a name="PushoverAttrexpire"></a><code>expire</code><br>
  1520. 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.
  1521. </li>
  1522. <li><a name="PushoverAttrretry"></a><code>retry</code><br>
  1523. 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.
  1524. </li>
  1525. <li><a name="PushoverAttrsound"></a><code>sound</code><br>
  1526. 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.
  1527. </li>
  1528. </ul>
  1529. <br>
  1530. <a name="PushoverEvents"></a>
  1531. <b>Generated events:</b>
  1532. <ul>
  1533. N/A
  1534. </ul>
  1535. </ul>
  1536. =end html_DE
  1537. =cut