root/lang/perl/tiarra/trunk/module/Auto/Notify.pm @ 39035

Revision 39035, 20.0 kB (checked in by topia, 21 months ago)

* add nma (not tested, but api success)
* add url-format and event-format support for prowl.
* switched to api.prowlapp.com.

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id URL Date Rev Author
Line 
1# -----------------------------------------------------------------------------
2# $Id$
3# -----------------------------------------------------------------------------
4package Auto::Notify;
5use strict;
6use warnings;
7use base qw(Module);
8use Module::Use qw(Auto::AliasDB Tools::HTTPClient Auto::Utils);
9use Auto::AliasDB;
10use Tools::HTTPClient; # >= r11345
11use Auto::Utils;
12use HTTP::Request::Common;
13
14sub new {
15  my ($class) = shift;
16  my $this = $class->SUPER::new(@_);
17
18  $this->config_reload(undef);
19
20  return $this;
21}
22
23sub config_reload {
24  my ($this, $old_config) = @_;
25
26  my $regex = join '|', (
27    (map { "(?:$_)" } $this->config->regex_keyword('all')),
28    (map { "(?i:\Q$_\E)" } map { split /,/ } $this->config->keyword('all')),
29   );
30  eval {
31    $this->{regex} = qr/$regex/;
32  }; if ($@) {
33    $this->_runloop->notify_error($@);
34  }
35
36  $this->{blocks} = [];
37  foreach my $blockname (map {split /\s+/} $this->config->blocks('all')) {
38      my $block = $this->config->get($blockname, 'block');
39      if (!defined $block) {
40          die "not found block: $blockname";
41      }
42      my $type = $block->type;
43      if (!defined $type) {
44          die "type definition not found in block";
45      }
46      my $meth = $this->can('config_'.$type);
47      if (!defined $meth) {
48          die "unknown type: $type";
49      }
50      $this->$meth($block);
51      push(@{$this->{blocks}}, $block);
52  }
53
54  return $this;
55}
56
57sub message_arrived {
58  my ($this,$msg,$sender) = @_;
59  my @result = ($msg);
60
61  # サーバーからのメッセージか?
62  if ($sender->isa('IrcIO::Server')) {
63      # PRIVMSGか?
64      if ($msg->command eq 'PRIVMSG') {
65          my $text = $msg->param(1);
66          my $full_ch_name = $msg->param(0);
67
68          if ($text =~ $this->{regex} && Mask::match_deep_chan(
69              [Mask::array_or_all_chan($this->config->mask('all'))],
70              $msg->prefix,$full_ch_name)) {
71
72              foreach my $block (@{$this->{blocks}}) {
73                  my $type = $block->type;
74                  my $meth = $this->can('send_'.$type);
75                  eval {
76                      $this->$meth($block, $text, $msg, $sender, $full_ch_name);
77                  }; if ($@) {
78                      $this->_runloop->notify_warn(__PACKAGE__." send failed: $@");
79                  }
80              }
81
82          }
83      }
84  }
85
86  return @result;
87}
88
89sub strip_mirc_formatting {
90    my ($this, $text) = @_;
91    $text =~ s/(?:\x03\d\d?(?:,\d\d?)?|[\x0f\x02\x1f\x16])//g;
92    $text;
93}
94
95sub config_im_kayac {
96    my ($this, $config) = @_;
97
98    if ($config->secret) {
99        # signature required
100        require Digest::SHA;
101    }
102
103    1;
104}
105
106sub send_im_kayac {
107    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
108
109    my $url = "http://im.kayac.com/api/post/" . $config->user;
110    $text = Auto::AliasDB->stdreplace(
111        $msg->prefix,
112        $config->format || $this->config->format || '[tiarra][#(channel):#(nick.now)] #(text)',
113        $msg, $sender,
114        channel => $full_ch_name,
115        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
116        text => $this->strip_mirc_formatting($text),
117       );
118    my @data = (message => $text);
119    if ($config->secret) {
120        push(@data, sig => Digest::SHA->new(1)
121                 ->add($text . $config->secret)->hexdigest);
122    } elsif ($config->password) {
123        push(@data, password => $config->password);
124    }
125    my $runloop = $this->_runloop;
126    Tools::HTTPClient->new(
127        Request => POST($url, \@data),
128       )->start(
129           Callback => sub {
130               my $stat = shift;
131               if (!ref($stat)) {
132                   $runloop->notify_warn(__PACKAGE__." im.kayac.com: post failed: $stat");
133               } elsif ($stat->{Content} !~ /"result":\s*"(?:ok|posted)"/) {
134                   # http://im.kayac.com/#docs
135                   # (but actually responce is '"result": "ok"')
136                   (my $content = $stat->{Content}) =~ s/\s+/ /;
137                   $runloop->notify_warn(__PACKAGE__." im.kayac.com: post failed: $content");
138               }
139           },
140          );
141}
142
143
144sub config_prowl {
145    my ($this, $config) = @_;
146
147    require Crypt::SSLeay; # https support
148    require URI;
149
150    my $url = URI->new("https://api.prowlapp.com/publicapi/verify");
151    $url->query_form(apikey => $config->apikey);
152    my $runloop = $this->_runloop;
153    Tools::HTTPClient->new(
154        Request => GET($url->as_string()),
155       )->start(
156           Callback => sub {
157               my $stat = shift;
158               $runloop->notify_warn(__PACKAGE__." prowl: verify failed: $stat")
159                   unless ref($stat);
160               ## FIXME: check response (should check 'error')
161           },
162          );
163}
164
165sub send_prowl {
166    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
167
168    my $url = URI->new("https://api.prowlapp.com/publicapi/add");
169    $text = Auto::AliasDB->stdreplace(
170        $msg->prefix,
171        $config->format || $this->config->format || '[tiarra][#(channel):#(nick.now)] #(text)',
172        $msg, $sender,
173        channel => $full_ch_name,
174        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
175        text => $this->strip_mirc_formatting($text),
176       );
177    my $event;
178    if (defined $config->event_format) {
179        $event = Auto::AliasDB->stdreplace(
180            $msg->prefix,
181            $config->event_format,
182            $msg, $sender,
183            channel => $full_ch_name,
184            raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
185            text => $text,
186           );
187    } else {
188        $event = $config->event || 'keyword';
189    }
190    my $uri = Auto::AliasDB->stdreplace(
191        $msg->prefix,
192        $config->url_format || '', ## config and param are "URL"
193        $msg, $sender,
194        channel => $full_ch_name,
195        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
196        text => $text,
197       );
198    my @data = (apikey => $config->apikey,
199                priority => $config->priority || 0,
200                application => $config->application || 'tiarra',
201                event => $event,
202                ($uri ne "" ? (url => $uri) : ()),
203                description => $text);
204    $url->query_form(@data);
205
206    my $runloop = $this->_runloop;
207    Tools::HTTPClient->new(
208        Request => GET($url->as_string()),
209       )->start(
210           Callback => sub {
211               my $stat = shift;
212               if (!ref($stat)) {
213                   $runloop->notify_warn(__PACKAGE__." prowl: post failed: $stat");
214               } elsif ($stat->{Content} !~ /<success /) {
215                   (my $content = $stat->{Content}) =~ s/\s+/ /;
216                   $runloop->notify_warn(__PACKAGE__." prowl: post failed: $content");
217               }
218           },
219          );
220}
221
222sub config_boxcar {
223    my ($this, $config) = @_;
224
225    my $runloop = $this->_runloop;
226    if (!$config->provider_key) {
227        # growl mode
228        require Crypt::SSLeay; # https support
229        if (!$config->user || !$config->password) {
230            $runloop->notify_warn(__PACKAGE__." boxcar (Growl): please set user and/or password");
231        }
232    } elsif ($config->email_hash) {
233        # ok
234    } elsif ($config->email) {
235        # needs to hash email
236        require Digest::MD5;
237    } elsif ($config->token && $config->secret) {
238        # ok
239    } else {
240        $runloop->notify_warn(__PACKAGE__." boxcar (Provider): please set email-hash, email or token and secret");
241    }
242
243}
244
245sub send_boxcar {
246    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
247
248    $text = $this->strip_mirc_formatting($text);
249    my $screen_name = Auto::AliasDB->stdreplace(
250        $msg->prefix,
251        $config->screenname_format || '[tiarra][#(channel):#(nick.now)]',
252        $msg, $sender,
253        channel => $full_ch_name,
254        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
255        text => $text,
256       );
257    $text = Auto::AliasDB->stdreplace(
258        $msg->prefix,
259        $config->format || $this->config->format || '#(text)',
260        $msg, $sender,
261        channel => $full_ch_name,
262        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
263        text => $text,
264       );
265    my @data = ('notification[from_screen_name]' => $screen_name,
266                'notification[message]' => $text);
267
268    my $runloop = $this->_runloop;
269    if (!$config->provider_key) {
270        # Growl mode
271        Tools::HTTPClient->new(
272            Request => POST("https://boxcar.io/notifications", \@data),
273           )->start(
274               Callback => sub {
275                   my $stat = shift;
276                   if (!ref($stat)) {
277                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $stat");
278                   } elsif ($stat->{Content} !~ /^\s*$/) {
279                       (my $content = $stat->{Content}) =~ s/\s+/ /;
280                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $content");
281                   }
282               },
283              );
284    } else {
285        if ($config->email_hash) {
286            push(@data, email=>$config->email_hash);
287        } elsif ($config->email) {
288            push(@data, email=>Digest::MD5->new->add($config->email)->hexdigest);
289        } else {
290            push(@data,
291                 token => $config->token,
292                 secret => $config->secret);
293        }
294        Tools::HTTPClient->new(
295            Request => POST("http://boxcar.io/devices/providers/".
296                                $config->provider_key."/notifications", \@data),
297           )->start(
298               Callback => sub {
299                   my $stat = shift;
300                   if (!ref($stat)) {
301                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $stat");
302                   } elsif ($stat->{Content} !~ /^\s*$/) {
303                       (my $content = $stat->{Content}) =~ s/\s+/ /;
304                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $content");
305                   }
306               },
307              );
308
309    }
310
311}
312
313
314sub config_notifo {
315    my ($this, $config) = @_;
316
317    require Crypt::SSLeay; # https support
318    require MIME::Base64;
319
320    return # subscribe_user is not work with user account
321        if (!defined($config->to) || $config->user eq $config->to);
322    my $url = "https://api.notifo.com/v1/subscribe_user";
323    my $runloop = $this->_runloop;
324    Tools::HTTPClient->new(
325        Request => POST($url, [username => $config->user],
326                        Authorization => 'Basic '.
327                            MIME::Base64::encode($config->user .':'.$config->secret, "")),
328       )->start(
329           Callback => sub {
330               my $stat = shift;
331               if (!ref($stat)) {
332                   $runloop->notify_warn(__PACKAGE__." notifo: verify failed: $stat");
333               } elsif ($stat->{Content} !~ /"status":\s*"success"[,}]/) {
334                   (my $content = $stat->{Content}) =~ s/\s+/ /;
335                   $runloop->notify_warn(__PACKAGE__." notifo: verify failed: $content");
336               }
337           },
338          );
339}
340
341sub send_notifo {
342    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
343
344    my $url = "https://api.notifo.com/v1/send_notification";
345    $text = $this->strip_mirc_formatting($text);
346    my $title = Auto::AliasDB->stdreplace(
347        $msg->prefix,
348        $config->title_format || '#(channel):#(nick.now)',
349        $msg, $sender,
350        channel => $full_ch_name,
351        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
352        text => $text,
353       );
354    my $uri = Auto::AliasDB->stdreplace(
355        $msg->prefix,
356        $config->uri_format || '',
357        $msg, $sender,
358        channel => $full_ch_name,
359        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
360        text => $text,
361       );
362    $text = Auto::AliasDB->stdreplace(
363        $msg->prefix,
364        $config->format || $this->config->format || '#(text)',
365        $msg, $sender,
366        channel => $full_ch_name,
367        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
368        text => $text,
369       );
370    my $data = [label => $config->label || 'tiarra',
371                title => $title,
372                to => $config->to || $config->user,
373                ((defined($uri) && $uri ne "") ? (uri => $uri) : ()),
374                msg => $text];
375    my $runloop = $this->_runloop;
376    Tools::HTTPClient->new(
377        Request => POST($url, $data, Authorization => 'Basic '.
378                            MIME::Base64::encode($config->user .':'.$config->secret, "")),
379       )->start(
380           Callback => sub {
381               my $stat = shift;
382               if (!ref($stat)) {
383                   $runloop->notify_warn(__PACKAGE__." notifo: post failed: $stat");
384               } elsif ($stat->{Content} !~ /"status":\s*"success"[,}]/) {
385                   (my $content = $stat->{Content}) =~ s/\s+/ /;
386                   $runloop->notify_warn(__PACKAGE__." notifo: post failed: $content");
387               }
388           },
389          );
390}
391
392
393sub config_nma {
394    my ($this, $config) = @_;
395
396    # I don't have a good feeling to NMA, but Prowl didn't support
397    #  Android, on 2011-09-30.
398    # see also http://www.cocoaforge.com/viewtopic.php?f=45&t=20765#p129361
399    # and check send_prowl and send_nma.
400
401    require Crypt::SSLeay; # https support
402    require URI;
403
404    foreach my $apikey (split(/,/, $config->apikey)) {
405        my $url = URI->new("https://www.notifymyandroid.com/publicapi/verify");
406        $url->query_form(apikey => $apikey,
407                         (defined $config->developerkey ?
408                              (developerkey => $config->developerkey) : ()));
409        my $runloop = $this->_runloop;
410        Tools::HTTPClient->new(
411            Request => GET($url->as_string()),
412           )->start(
413               Callback => sub {
414                   my $stat = shift;
415                   $runloop->notify_warn(__PACKAGE__." NMA: verify failed: $stat")
416                       unless ref($stat);
417                   ## FIXME: check response (should check 'error')
418               },
419              );
420    }
421}
422
423sub send_nma {
424    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
425
426    my $url = URI->new("https://www.notifymyandroid.com/publicapi/notify");
427    $text = Auto::AliasDB->stdreplace(
428        $msg->prefix,
429        $config->format || $this->config->format || '[tiarra][#(channel):#(nick.now)] #(text)',
430        $msg, $sender,
431        channel => $full_ch_name,
432        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
433        text => $this->strip_mirc_formatting($text),
434       );
435    my $event = Auto::AliasDB->stdreplace(
436        $msg->prefix,
437        $config->event_format || 'keyword',
438        $msg, $sender,
439        channel => $full_ch_name,
440        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
441        text => $text,
442       );
443    my @data = (apikey => $config->apikey,
444                priority => $config->priority || 0,
445                application => $config->application || 'tiarra',
446                event => $event,
447                description => $text,
448                (defined $config->developerkey ?
449                     (developerkey => $config->developerkey) : ()));
450    $url->query_form(@data);
451
452    my $runloop = $this->_runloop;
453    Tools::HTTPClient->new(
454        Request => GET($url->as_string()),
455       )->start(
456           Callback => sub {
457               my $stat = shift;
458               if (!ref($stat)) {
459                   $runloop->notify_warn(__PACKAGE__." NMA: post failed: $stat");
460               } elsif ($stat->{Content} !~ /<success /) {
461                   (my $content = $stat->{Content}) =~ s/\s+/ /;
462                   $runloop->notify_warn(__PACKAGE__." NMA: post failed: $content");
463               }
464           },
465          );
466}
467
468
4691;
470
471=pod
472info: 名前が呼ばれると、その発言をim.kayac.comに送信する
473default: off
474
475# 反応する人のマスクを指定します。
476# 省略すると全員に反応します。
477mask: * *!*@*
478
479# 反応するキーワードを正規表現で指定します。
480# 複数指定したい時は複数行指定してください。
481-regex-keyword: (?i:fugahoge)
482
483# 反応するキーワードを指定します。
484# 複数指定したい時は,(コンマ)で区切るか、複数行指定してください。
485keyword: hoge
486
487# メッセージのフォーマットを指定します。
488# デフォルト値: [tiarra][#(channel):#(nick.now)] #(text)
489# #(channel) のかわりに #(raw_channel) を利用するとネットワーク名がつきません。
490format: [tiarra][#(channel):#(nick.now)] #(text)
491
492# 使用するブロックを指定します
493-blocks: im prowl boxcar-growl boxcar-provider notifo nma
494
495im {
496
497# 通知先のタイプを指定します。
498type: im_kayac
499
500# im.kayac.comで登録したユーザ名を入力します。
501# im.kayac.comについては http://im.kayac.com/#docs を参考にしてください。
502user: username
503
504# im.kayac.comで秘密鍵認証を選択した場合は設定してください。
505# 省略すると認証なしになります。
506-secret: some secret
507
508# im.kayac.comでパスワード認証を選択した場合は設定してください。
509# 省略すると認証なしになります。
510# secret と両方指定した場合は secret が優先されています。
511-password: some password
512
513}
514
515prowl {
516
517# 通知先のタイプを指定します。
518type: prowl
519
520# 通知先ごとにフォーマットを指定できます。
521# この例では先頭に時刻を追加しています。
522-format: #(date:%H:%M:%S) #(text)
523
524# Prowl で表示された apikey を入力します。
525# Prowl については http://prowl.weks.net/ を参考にしてください。
526-apikey: XXXXXX
527
528# イベントのフォーマットを指定できます。
529# 省略すると event の設定が利用されます。
530event-format: #(channel):#(nick.now)
531
532# URLのフォーマットを指定できます。
533# 省略すると通知にURLを含めません。
534# 現状の機構ではURLをエスケープする手段がないので、固定値以外はお勧めしません。
535# また、 URL を指定するとアプリ側でのredirect設定は無視されるようです。
536url-format:
537
538# イベントを指定します。(固定値)
539# event-format が指定された場合はそちらが優先されます。
540event: keyword
541
542
543# http://forums.cocoaforge.com/viewtopic.php?f=45&t=20339
544priority: 0
545application: tiarra
546
547}
548
549boxcar-growl {
550# 利用する前にサービスリストに Growl を追加しておいてください。
551
552type: boxcar
553
554# Boxcar のユーザー名を指定します。必須です。
555user:
556
557# Boxcar のパスワードを指定します。必須です。
558password:
559
560# スクリーンネームのフォーマットを指定できます。
561# デフォルト値: [tiarra][#(channel):#(nick.now)]
562screenname-format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
563
564# 通知先ごとにフォーマットを指定できます。
565# この例では先頭に時刻を追加しています。
566# Boxcar ではスクリーンネームが別になるので、個別指定をお勧めします。
567format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
568
569}
570
571boxcar-provider {
572# 自分用 provider を立てて利用するタイプです。
573# http://boxcar.io/site/providers からサインアップしてください。
574# このとき、 curl のコマンドライン中にある token と secret は
575# サインアップ直後にしか表示されないので、忘れずメモしてください。
576# (もちろんwebhookを立てればいつでも取得できますが……)
577type: boxcar
578
579# provider の API key を指定します。これがないと Growl モードになります。
580provider-key: XXXXXX
581
582# 通知先の指定をします。
583# token と secret, email, email-hash のいずれかを指定するようにしてください。
584
585# トークン。サインアップ直後の curl のコマンドライン中にあります。
586-token: XXXXXX
587
588# シークレット。サインアップ直後の curl のコマンドライン中にあります。
589-secret: XXXXXXXX
590
591# メールアドレス。 Digest::MD5 が必要です。
592-email: XXXX@XXXX
593
594# メールアドレスのMD5ハッシュ。 Digest::MD5 は必要ありません。
595-email-hash: xxxxxx
596
597# スクリーンネームのフォーマットを指定できます。
598# デフォルト値: [tiarra][#(channel):#(nick.now)]
599screenname-format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
600
601# 通知先ごとにフォーマットを指定できます。
602# この例では先頭に時刻を追加しています。
603# Boxcar ではスクリーンネームが別になるので、個別指定をお勧めします。
604format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
605
606}
607
608notifo {
609
610# 通知先のタイプを指定します。
611type: notifo
612
613# noifo の Settings ページにある API Username を指定します。
614# http://notifo.com/user/settings
615user: XXXXXXX
616
617# noifo の Settings ページにある API Secret を指定します。
618# http://notifo.com/user/settings
619secret: XXXXXXXXXXXXXXXXXXXXXX
620
621# ラベルを指定します。
622# サービスアカウントでは無視されます。
623label: tiarra
624
625# 通知先のユーザ名を指定します。
626# ユーザアカウントでは無視されます。省略した場合は user に通知します。
627-to: XXXXXXXXXXXXX
628
629# タイトルのフォーマットを指定できます。
630# デフォルト値: #(channel):#(nick.now)
631title-format: #(channel):#(nick.now)
632
633# URIのフォーマットを指定できます。
634# 省略すると通知にURIを含めません。
635# 現状の機構ではURIをエスケープする手段がないので、固定値以外はお勧めしません。
636uri-format:
637
638# 通知先ごとにフォーマットを指定できます。
639# この例では先頭に時刻を追加しています。
640format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
641
642}
643
644nma {
645
646# 通知先のタイプを指定します。
647# Notify My Android には nma を指定してください。
648type: nma
649
650# 通知先ごとにフォーマットを指定できます。
651# この例では先頭に時刻を追加しています。
652format: #(date:%H:%M:%S) #(text)
653
654# NMA で表示された apikey を入力します。
655# https://www.notifymyandroid.com/account.php
656# カンマで区切ると複数のAPIキーを指定することができます。
657-apikey: XXXXXX
658
659# イベントのフォーマットを指定できます。
660# デフォルト値: keyword
661event-format: #(channel):#(nick.now)
662
663# https://www.notifymyandroid.com/api.php
664priority: 0
665application: tiarra
666-developerkey:
667
668}
669
670
671=cut
Note: See TracBrowser for help on using the browser.