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

Revision 38984, 15.8 kB (checked in by topia, 22 months ago)

add experimental notifo support.

  • 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://prowl.weks.net/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__." 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://prowl.weks.net/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 @data = (apikey => $config->apikey,
178                priority => $config->priority || 0,
179                application => $config->application || 'tiarra',
180                event => $config->event || 'keyword',
181                description => $text);
182    $url->query_form(@data);
183
184    my $runloop = $this->_runloop;
185    Tools::HTTPClient->new(
186        Request => GET($url->as_string()),
187       )->start(
188           Callback => sub {
189               my $stat = shift;
190               if (!ref($stat)) {
191                   $runloop->notify_warn(__PACKAGE__." prowl: post failed: $stat");
192               } elsif ($stat->{Content} !~ /<success /) {
193                   (my $content = $stat->{Content}) =~ s/\s+/ /;
194                   $runloop->notify_warn(__PACKAGE__." prowl: post failed: $content");
195               }
196           },
197          );
198}
199
200sub config_boxcar {
201    my ($this, $config) = @_;
202
203    my $runloop = $this->_runloop;
204    if (!$config->provider_key) {
205        # growl mode
206        require Crypt::SSLeay; # https support
207        if (!$config->user || !$config->password) {
208            $runloop->notify_warn(__PACKAGE__." boxcar (Growl): please set user and/or password");
209        }
210    } elsif ($config->email_hash) {
211        # ok
212    } elsif ($config->email) {
213        # needs to hash email
214        require Digest::MD5;
215    } elsif ($config->token && $config->secret) {
216        # ok
217    } else {
218        $runloop->notify_warn(__PACKAGE__." boxcar (Provider): please set email-hash, email or token and secret");
219    }
220
221}
222
223sub send_boxcar {
224    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
225
226    $text = $this->strip_mirc_formatting($text);
227    my $screen_name = Auto::AliasDB->stdreplace(
228        $msg->prefix,
229        $config->screenname_format || '[tiarra][#(channel):#(nick.now)]',
230        $msg, $sender,
231        channel => $full_ch_name,
232        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
233        text => $text,
234       );
235    $text = Auto::AliasDB->stdreplace(
236        $msg->prefix,
237        $config->format || $this->config->format || '#(text)',
238        $msg, $sender,
239        channel => $full_ch_name,
240        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
241        text => $text,
242       );
243    my @data = ('notification[from_screen_name]' => $screen_name,
244                'notification[message]' => $text);
245
246    my $runloop = $this->_runloop;
247    if (!$config->provider_key) {
248        # Growl mode
249        Tools::HTTPClient->new(
250            Request => POST("https://boxcar.io/notifications", \@data),
251           )->start(
252               Callback => sub {
253                   my $stat = shift;
254                   if (!ref($stat)) {
255                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $stat");
256                   } elsif ($stat->{Content} !~ /^\s*$/) {
257                       (my $content = $stat->{Content}) =~ s/\s+/ /;
258                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $content");
259                   }
260               },
261              );
262    } else {
263        if ($config->email_hash) {
264            push(@data, email=>$config->email_hash);
265        } elsif ($config->email) {
266            push(@data, email=>Digest::MD5->new->add($config->email)->hexdigest);
267        } else {
268            push(@data,
269                 token => $config->token,
270                 secret => $config->secret);
271        }
272        Tools::HTTPClient->new(
273            Request => POST("http://boxcar.io/devices/providers/".
274                                $config->provider_key."/notifications", \@data),
275           )->start(
276               Callback => sub {
277                   my $stat = shift;
278                   if (!ref($stat)) {
279                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $stat");
280                   } elsif ($stat->{Content} !~ /^\s*$/) {
281                       (my $content = $stat->{Content}) =~ s/\s+/ /;
282                       $runloop->notify_warn(__PACKAGE__." boxcar: post failed: $content");
283                   }
284               },
285              );
286
287    }
288
289}
290
291
292sub config_notifo {
293    my ($this, $config) = @_;
294
295    require Crypt::SSLeay; # https support
296    require MIME::Base64;
297
298    return # subscribe_user is not work with user account
299        if (!defined($config->to) || $config->user eq $config->to);
300    my $url = "https://api.notifo.com/v1/subscribe_user";
301    my $runloop = $this->_runloop;
302    Tools::HTTPClient->new(
303        Request => POST($url, [username => $config->user],
304                        Authorization => 'Basic '.
305                            MIME::Base64::encode($config->user .':'.$config->secret, "")),
306       )->start(
307           Callback => sub {
308               my $stat = shift;
309               if (!ref($stat)) {
310                   $runloop->notify_warn(__PACKAGE__." notifo: verify failed: $stat");
311               } elsif ($stat->{Content} !~ /"status":\s*"success"[,}]/) {
312                   (my $content = $stat->{Content}) =~ s/\s+/ /;
313                   $runloop->notify_warn(__PACKAGE__." notifo: verify failed: $content");
314               }
315           },
316          );
317}
318
319sub send_notifo {
320    my ($this, $config, $text, $msg, $sender, $full_ch_name) = @_;
321
322    my $url = "https://api.notifo.com/v1/send_notification";
323    $text = $this->strip_mirc_formatting($text);
324    my $title = Auto::AliasDB->stdreplace(
325        $msg->prefix,
326        $config->title_format || '#(channel):#(nick.now)',
327        $msg, $sender,
328        channel => $full_ch_name,
329        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
330        text => $text,
331       );
332    my $uri = Auto::AliasDB->stdreplace(
333        $msg->prefix,
334        $config->uri_format || '',
335        $msg, $sender,
336        channel => $full_ch_name,
337        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
338        text => $text,
339       );
340    $text = Auto::AliasDB->stdreplace(
341        $msg->prefix,
342        $config->format || $this->config->format || '#(text)',
343        $msg, $sender,
344        channel => $full_ch_name,
345        raw_channel => Auto::Utils::get_raw_ch_name($msg, 0),
346        text => $text,
347       );
348    my $data = [label => $config->label || 'tiarra',
349                title => $title,
350                to => $config->to || $config->user,
351                ((defined($uri) && $uri ne "") ? (uri => $uri) : ()),
352                msg => $text];
353    my $runloop = $this->_runloop;
354    Tools::HTTPClient->new(
355        Request => POST($url, $data, Authorization => 'Basic '.
356                            MIME::Base64::encode($config->user .':'.$config->secret, "")),
357       )->start(
358           Callback => sub {
359               my $stat = shift;
360               if (!ref($stat)) {
361                   $runloop->notify_warn(__PACKAGE__." notifo: post failed: $stat");
362               } elsif ($stat->{Content} !~ /"status":\s*"success"[,}]/) {
363                   (my $content = $stat->{Content}) =~ s/\s+/ /;
364                   $runloop->notify_warn(__PACKAGE__." notifo: post failed: $content");
365               }
366           },
367          );
368}
369
370
3711;
372
373=pod
374info: 名前が呼ばれると、その発言をim.kayac.comに送信する
375default: off
376
377# 反応する人のマスクを指定します。
378# 省略すると全員に反応します。
379mask: * *!*@*
380
381# 反応するキーワードを正規表現で指定します。
382# 複数指定したい時は複数行指定してください。
383-regex-keyword: (?i:fugahoge)
384
385# 反応するキーワードを指定します。
386# 複数指定したい時は,(コンマ)で区切るか、複数行指定してください。
387keyword: hoge
388
389# メッセージのフォーマットを指定します。
390# デフォルト値: [tiarra][#(channel):#(nick.now)] #(text)
391# #(channel) のかわりに #(raw_channel) を利用するとネットワーク名がつきません。
392format: [tiarra][#(channel):#(nick.now)] #(text)
393
394# 使用するブロックを指定します
395-blocks: im prowl boxcar-growl boxcar-provider notifo
396
397im {
398
399# 通知先のタイプを指定します。
400type: im_kayac
401
402# im.kayac.comで登録したユーザ名を入力します。
403# im.kayac.comについては http://im.kayac.com/#docs を参考にしてください。
404user: username
405
406# im.kayac.comで秘密鍵認証を選択した場合は設定してください。
407# 省略すると認証なしになります。
408-secret: some secret
409
410# im.kayac.comでパスワード認証を選択した場合は設定してください。
411# 省略すると認証なしになります。
412# secret と両方指定した場合は secret が優先されています。
413-password: some password
414
415}
416
417prowl {
418
419# 通知先のタイプを指定します。
420type: prowl
421
422# 通知先ごとにフォーマットを指定できます。
423# この例では先頭に時刻を追加しています。
424-format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
425
426# Prowl で表示された apikey を入力します。
427# Prowl については http://prowl.weks.net/ を参考にしてください。
428-apikey: XXXXXX
429
430# http://forums.cocoaforge.com/viewtopic.php?f=45&t=20339
431priority: 0
432application: tiarra
433event: keyword
434
435}
436
437boxcar-growl {
438# 利用する前にサービスリストに Growl を追加しておいてください。
439
440type: boxcar
441
442# Boxcar のユーザー名を指定します。必須です。
443user:
444
445# Boxcar のパスワードを指定します。必須です。
446password:
447
448# スクリーンネームのフォーマットを指定できます。
449# デフォルト値: [tiarra][#(channel):#(nick.now)]
450screenname-format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
451
452# 通知先ごとにフォーマットを指定できます。
453# この例では先頭に時刻を追加しています。
454# Boxcar ではスクリーンネームが別になるので、個別指定をお勧めします。
455format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
456
457}
458
459boxcar-provider {
460# 自分用 provider を立てて利用するタイプです。
461# http://boxcar.io/site/providers からサインアップしてください。
462# このとき、 curl のコマンドライン中にある token と secret は
463# サインアップ直後にしか表示されないので、忘れずメモしてください。
464# (もちろんwebhookを立てればいつでも取得できますが……)
465type: boxcar
466
467# provider の API key を指定します。これがないと Growl モードになります。
468provider-key: XXXXXX
469
470# 通知先の指定をします。
471# token と secret, email, email-hash のいずれかを指定するようにしてください。
472
473# トークン。サインアップ直後の curl のコマンドライン中にあります。
474-token: XXXXXX
475
476# シークレット。サインアップ直後の curl のコマンドライン中にあります。
477-secret: XXXXXXXX
478
479# メールアドレス。 Digest::MD5 が必要です。
480-email: XXXX@XXXX
481
482# メールアドレスのMD5ハッシュ。 Digest::MD5 は必要ありません。
483-email-hash: xxxxxx
484
485# スクリーンネームのフォーマットを指定できます。
486# デフォルト値: [tiarra][#(channel):#(nick.now)]
487screenname-format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
488
489# 通知先ごとにフォーマットを指定できます。
490# この例では先頭に時刻を追加しています。
491# Boxcar ではスクリーンネームが別になるので、個別指定をお勧めします。
492format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
493
494}
495
496notifo {
497
498# 通知先のタイプを指定します。
499type: notifo
500
501# noifo の Settings ページにある API Username を指定します。
502# http://notifo.com/user/settings
503user: XXXXXXX
504
505# noifo の Settings ページにある API Secret を指定します。
506# http://notifo.com/user/settings
507secret: XXXXXXXXXXXXXXXXXXXXXX
508
509# ラベルを指定します。
510# サービスアカウントでは無視されます。
511label: tiarra
512
513# 通知先のユーザ名を指定します。
514# ユーザアカウントでは無視されます。省略した場合は user に通知します。
515-to: XXXXXXXXXXXXX
516
517# タイトルのフォーマットを指定できます。
518# デフォルト値: #(channel):#(nick.now)
519title-format: #(channel):#(nick.now)
520
521# URIのフォーマットを指定できます。
522# 省略すると通知にURIを含めません。
523# 現状の機構ではURIをエスケープする手段がないので、固定値以外はお勧めしません。
524uri-format:
525
526# 通知先ごとにフォーマットを指定できます。
527# この例では先頭に時刻を追加しています。
528format: #(date:%H:%M:%S) [#(channel):#(nick.now)] #(text)
529
530}
531
532
533=cut
Note: See TracBrowser for help on using the browser.