| 1 | package App::MadEye::Plugin::Notify::XMPP; |
|---|
| 2 | use strict; |
|---|
| 3 | use warnings; |
|---|
| 4 | use base qw/App::MadEye::Plugin::Base/; |
|---|
| 5 | use Text::Truncate qw/truncstr/; |
|---|
| 6 | use Net::XMPP; |
|---|
| 7 | use Carp; |
|---|
| 8 | use Data::Dumper; |
|---|
| 9 | use Encode; |
|---|
| 10 | use XML::Stream; |
|---|
| 11 | |
|---|
| 12 | sub notify : Hook { |
|---|
| 13 | my ( $self, $context, $args ) = @_; |
|---|
| 14 | |
|---|
| 15 | my $config = $self->config->{config}; |
|---|
| 16 | |
|---|
| 17 | my ( $username, $componentname ) = split /@/, $config->{jid}; |
|---|
| 18 | $config->{host} ||= $componentname; |
|---|
| 19 | my $client = Net::XMPP::Client->new( debuglevel => 2 ); |
|---|
| 20 | my $connectattempts = 3; |
|---|
| 21 | my $connectsleep = 1; |
|---|
| 22 | my ($status, $error); |
|---|
| 23 | while (--$connectattempts >= 0) { |
|---|
| 24 | $status = $client->Connect( |
|---|
| 25 | hostname => $config->{host}, |
|---|
| 26 | port => $config->{port} || 5222, |
|---|
| 27 | tls => $config->{tls} || 0, |
|---|
| 28 | componentname => $componentname, |
|---|
| 29 | connectiontype => 'http', |
|---|
| 30 | ); |
|---|
| 31 | last if defined $status; |
|---|
| 32 | sleep $connectsleep; |
|---|
| 33 | } |
|---|
| 34 | unless (defined $status) { |
|---|
| 35 | carp "[FATAL] failed to connect ".join(':',$config->{host}, $config->{port}) . ' : ' . $client->GetErrorCode->{text}; |
|---|
| 36 | return; |
|---|
| 37 | } |
|---|
| 38 | |
|---|
| 39 | { |
|---|
| 40 | |
|---|
| 41 | # quick hack to connect Google Talk |
|---|
| 42 | # override XML::Stream-1.22 by hirose31++ |
|---|
| 43 | no warnings 'redefine'; |
|---|
| 44 | *XML::Stream::SASLClient = sub { |
|---|
| 45 | my $self = shift; |
|---|
| 46 | my $sid = shift; |
|---|
| 47 | my $username = shift; |
|---|
| 48 | my $password = shift; |
|---|
| 49 | |
|---|
| 50 | my $mechanisms = $self->GetStreamFeature( $sid, "xmpp-sasl" ); |
|---|
| 51 | |
|---|
| 52 | return unless defined($mechanisms); |
|---|
| 53 | |
|---|
| 54 | my $sasl = new Authen::SASL( |
|---|
| 55 | mechanism => join( " ", @{$mechanisms} ), |
|---|
| 56 | callback => { |
|---|
| 57 | authname => $username . "@" |
|---|
| 58 | . ( |
|---|
| 59 | $self->{SIDS}->{$sid}->{to} |
|---|
| 60 | or $self->{SIDS}->{$sid}->{hostname} |
|---|
| 61 | ), |
|---|
| 62 | user => $username, |
|---|
| 63 | pass => $password |
|---|
| 64 | } |
|---|
| 65 | ); |
|---|
| 66 | |
|---|
| 67 | $self->{SIDS}->{$sid}->{sasl}->{client} = $sasl->client_new(); |
|---|
| 68 | $self->{SIDS}->{$sid}->{sasl}->{username} = $username; |
|---|
| 69 | $self->{SIDS}->{$sid}->{sasl}->{password} = $password; |
|---|
| 70 | $self->{SIDS}->{$sid}->{sasl}->{authed} = 0; |
|---|
| 71 | $self->{SIDS}->{$sid}->{sasl}->{done} = 0; |
|---|
| 72 | |
|---|
| 73 | $self->SASLAuth($sid); |
|---|
| 74 | }; |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | ( $status, $error ) = $client->AuthSend( |
|---|
| 78 | username => $username, |
|---|
| 79 | password => $config->{password}, |
|---|
| 80 | resource => 'MadEye', |
|---|
| 81 | ); |
|---|
| 82 | unless ( $status and $status eq 'ok' ) { |
|---|
| 83 | carp "[FATAL] authentication failure : $username, $status, $error"; |
|---|
| 84 | return; |
|---|
| 85 | } |
|---|
| 86 | |
|---|
| 87 | for my $to (@{ $config->{recipients} }) { |
|---|
| 88 | $client->MessageSend( |
|---|
| 89 | to => $to, |
|---|
| 90 | body => encode('utf-8', _format( $args, $config->{cutoff_length} )), |
|---|
| 91 | ); |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | $client->Disconnect(); |
|---|
| 95 | } |
|---|
| 96 | |
|---|
| 97 | sub _format { |
|---|
| 98 | my ( $args, $cutoff_length ) = @_; |
|---|
| 99 | |
|---|
| 100 | my $text = ''; |
|---|
| 101 | while ( my ( $module, $targets ) = each %$args ) { |
|---|
| 102 | $module = _moniker($module); |
|---|
| 103 | $text .= "= $module\n" |
|---|
| 104 | . truncstr( _format_target($targets), $cutoff_length ) . "\n"; |
|---|
| 105 | } |
|---|
| 106 | $text; |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | sub _format_target { |
|---|
| 110 | my $targets = shift; |
|---|
| 111 | |
|---|
| 112 | my $text = ''; |
|---|
| 113 | for my $target (@$targets) { |
|---|
| 114 | $text .= "- $target->{target}\n"; |
|---|
| 115 | $text .= "$target->{message}\n"; |
|---|
| 116 | } |
|---|
| 117 | $text; |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | sub _moniker { |
|---|
| 121 | my $module = shift; |
|---|
| 122 | $module =~ s/.+:://; |
|---|
| 123 | $module; |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | 1; |
|---|
| 127 | __END__ |
|---|
| 128 | |
|---|
| 129 | =head1 NAME |
|---|
| 130 | |
|---|
| 131 | App::MadEye::Plugin::Notify::XMPP - notify with XMPP |
|---|
| 132 | |
|---|
| 133 | =head1 SYNOPSIS |
|---|
| 134 | |
|---|
| 135 | - module: Notify::XMPP |
|---|
| 136 | config: |
|---|
| 137 | jid: example@gmail.com |
|---|
| 138 | password: YOUR PASSWORD |
|---|
| 139 | host: talk.google.com |
|---|
| 140 | cutoff_length: 1000 |
|---|
| 141 | tls: 1 |
|---|
| 142 | recipients: |
|---|
| 143 | - example@gmail.com |
|---|
| 144 | |
|---|
| 145 | =head1 SCHEMA |
|---|
| 146 | |
|---|
| 147 | type: map |
|---|
| 148 | mapping: |
|---|
| 149 | port: |
|---|
| 150 | type: int |
|---|
| 151 | required: no |
|---|
| 152 | jid: |
|---|
| 153 | type: str |
|---|
| 154 | required: yes |
|---|
| 155 | password: |
|---|
| 156 | required: yes |
|---|
| 157 | type: str |
|---|
| 158 | host: |
|---|
| 159 | required: no |
|---|
| 160 | type: str |
|---|
| 161 | cutoff_length: |
|---|
| 162 | required: yes |
|---|
| 163 | type: int |
|---|
| 164 | recipients: |
|---|
| 165 | type: any |
|---|
| 166 | required: yes |
|---|
| 167 | tls: |
|---|
| 168 | type: int |
|---|
| 169 | required: no |
|---|
| 170 | |
|---|
| 171 | =head1 AUTHOR |
|---|
| 172 | |
|---|
| 173 | Tokuhiro Matsuno |
|---|
| 174 | |
|---|
| 175 | =head1 THANKS TO |
|---|
| 176 | |
|---|
| 177 | hirose31++ # a lot of code stallen from http://d.hatena.ne.jp/hirose31/20060817/1155821365 |
|---|
| 178 | |
|---|
| 179 | =head1 SEE ALSO |
|---|
| 180 | |
|---|
| 181 | L<App::MadEye>, L<Net::XMPP> |
|---|
| 182 | |
|---|