]> ruin.nu Git - NDIRC.git/blob - Bot.pm
Universal scan parsing
[NDIRC.git] / Bot.pm
1 #**************************************************************************
2 #   Copyright (C) 2009 by Michael Andreen <harvATruinDOTnu>               *
3 #                                                                         *
4 #   This program is free software; you can redistribute it and/or modify  *
5 #   it under the terms of the GNU General Public License as published by  *
6 #   the Free Software Foundation; either version 2 of the License, or     *
7 #   (at your option) any later version.                                   *
8 #                                                                         *
9 #   This program is distributed in the hope that it will be useful,       *
10 #   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12 #   GNU General Public License for more details.                          *
13 #                                                                         *
14 #   You should have received a copy of the GNU General Public License     *
15 #   along with this program; if not, write to the                         *
16 #   Free Software Foundation, Inc.,                                       *
17 #   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
18 #**************************************************************************/
19 package NDIRC::Bot;
20
21 use strict;
22 use warnings;
23 use feature ':5.10';
24
25 use Moose;
26
27 use POE::Component::IRC::Common qw/irc_to_utf8/;
28 use POE::Session;
29 use POE::Kernel;
30 use POE::Component::IRC::Plugin::Logger;
31 use POE::Component::IRC::Plugin::BotTraffic;
32 use POE::Component::IRC::Plugin::Connector;
33 use POE::Component::IRC::Plugin::AutoJoin;
34 use POE::Component::IRC::Plugin::NickReclaim;
35
36 use Mojo::Discord;
37 use Mojo::IOLoop;
38
39 use NDIRC::Dispatcher;
40 use NDIRC::IrcContext;
41
42 use IO::File;
43
44 has disp => (
45         is => 'rw',
46         isa => 'Object',
47         lazy_build => 1
48 );
49
50 has discord => (
51         is => 'rw',
52         isa => 'Object'
53 );
54
55 has discord_name => (
56         is => 'rw',
57         isa => 'Str'
58 );
59
60 has discord_id => (
61         is => 'rw',
62         isa => 'Str'
63 );
64
65 has discord_channels => (
66         is => 'rw',
67         isa => 'HashRef',
68         default => sub { {} }
69 );
70
71 has targets => (
72         is => 'ro',
73         isa => 'HashRef[ArrayRef[Str]]',
74         default => sub{ {} },
75 );
76
77 # We registered for all events, this will produce some debug info.
78 sub _default {
79         my ($event, $args) = @_[ARG0 .. $#_];
80         my @output = ( "$event: " );
81
82         for my $arg (@$args) {
83                 if ( ref $arg eq 'ARRAY' ) {
84                         push( @output, '[' . join(', ', @$arg ) . ']' );
85                 }
86                 else {
87                         push ( @output, "'$arg'" );
88                 }
89         }
90         print join ' ', @output, "\n";
91         return 0;
92 }
93
94 my $irc;
95 sub irc {
96         return $irc;
97 }
98
99 sub _start {
100         my ($self,$kernel,$heap,$session) = @_[OBJECT,KERNEL,HEAP,SESSION];
101
102         # retrieve our component's object from the heap where we stashed it
103         $irc = $heap->{irc};
104         $kernel->sig( DIE => 'sig_DIE' );
105         $kernel->sig( USR1 => 'sig_usr1' );
106         $kernel->sig( USR2 => 'sig_usr2' );
107         $kernel->sig( INT => 'signal_handler' );
108
109         $irc->plugin_add( 'NickReclaim', POE::Component::IRC::Plugin::NickReclaim->new() );
110         $irc->plugin_add( 'AutoJoin', POE::Component::IRC::Plugin::NickReclaim->new() );
111         $irc->plugin_add( 'BotTraffic', POE::Component::IRC::Plugin::BotTraffic->new() );
112         $irc->plugin_add('Logger', POE::Component::IRC::Plugin::Logger->new(
113                 Path    => 'irclogs',
114                 DCC     => 0,
115                 Private => 1,
116                 Public  => 1,
117                 Sort_by_date => 1,
118                 Strip_color => 1,
119                 Strip_formatting => 1,
120                 Notices => 1,
121         ));
122
123         $heap->{connector} = POE::Component::IRC::Plugin::Connector->new(
124                 servers => [['irc.netgamers.org'], ['underworld.no.eu.netgamers.org']
125                         ,['firefly.no.eu.netgamers.org'], ['underworld.ca.us.netgamers.org'] ]
126         );
127         $irc->plugin_add( 'Connector' => $heap->{connector} );
128
129         $irc->yield( register => 'all' );
130         $irc->yield( connect => { server => 'irc.netgamers.org' } );
131
132         $kernel->delay( refresh => 60 );
133
134         if (my $f =  new IO::File 'discord'){
135                 my $user = <$f>;
136                 chomp $user;
137                 my $token = <$f>;
138                 chomp $token;
139
140                 $self->discord(Mojo::Discord->new(
141                                 'token'     => $token,
142                                 'name'      => $user,
143                                 'url'       => 'https://nd.ruin.nu',
144                                 'version'   => '1.0',
145                                 'callbacks' => {
146                                         'READY'          => sub { $self->discord_ready(@_) },
147                                         'MESSAGE_CREATE' => sub { $self->discord_message_create(@_) },
148                                         'GUILD_CREATE' => sub { $self->discord_guild_create(@_) },
149                                         'CHANNEL_CREATE' => sub { $self->discord_channel_create(@_) },
150                                 },
151                                 'reconnect' => 1,
152                                 'verbose'   => 1,
153                         ));
154                 $self->discord->init();
155         }
156         return;
157 }
158
159 sub auth {
160         my $heap = $_[HEAP];
161
162         if (my $f =  new IO::File 'auth'){
163                 my $user = <$f>;
164                 chomp $user;
165                 my $pass = <$f>;
166                 chomp $pass;
167                 $heap->{irc}->yield(qbot_auth => $user => $pass);
168         }
169 }
170
171 sub sig_usr1 {
172         my ($kernel,$heap) = @_[KERNEL,HEAP];
173
174         $kernel->yield( 'refresh' );
175 }
176
177 sub clear_constraint {
178         my $tc = shift;
179
180         while (1) {
181                 if (ref $tc eq 'MooseX::Meta::TypeConstraint::Structured'){
182                         for my $t (@{$tc->{type_constraints}}){
183                                 clear_constraint($t);
184                         }
185
186                 }
187                 if (ref $tc eq 'Moose::Meta::TypeConstraint::Parameterized'){
188                         clear_constraint($tc->{type_parameter});
189                 }
190                 last if ref $tc eq 'HASH';
191                 last if ref $tc eq '';
192                 if (defined $tc->{_type_constraint}){
193                         $tc = $tc->{_type_constraint};
194                 }elsif(defined $tc->{__type_constraint}){
195                         $tc = $tc->{__type_constraint};
196                 }else{
197                         last;
198                 }
199         }
200 }
201
202 sub clear_metains {
203         my $ins = shift;
204
205         for my $a (@{$ins->{attributes}}){
206                 for my $m (@{$a->{associated_methods}}){
207                         $m->{body} = undef;
208                 }
209                 clear_constraint($a->{isa});
210         }
211 }
212
213 sub clear_cycles {
214         my $c = shift;
215
216         for my $m (values %{$c->meta->{methods}}){
217                 clear_constraint($m->{type_constraint});
218
219                 my $ps = $m->{parsed_signature};
220                 for my $p (@{$ps->{_positional_params}->{params}}){
221                         clear_metains($p->{__MOP__}->{_meta_instance});
222                 }
223
224                 $m->{body} = undef;
225         }
226         clear_metains($c->meta->{_meta_instance});
227 }
228
229
230 sub sig_usr2 {
231         my $self = shift @_;
232
233         for my $c (values %{$self->disp->commands}){
234                 clear_cycles($c);
235         }
236
237         $self->disp($self->_build_disp);
238 }
239
240 sub _build_disp {
241         my ($self) = @_;
242         my $disp = new NDIRC::Dispatcher;
243
244         if (my $commands = new IO::File 'commands'){
245                 my @commands = split /\W+/, do{local $/; <$commands>};
246                 say "Loading commands from: @commands";
247                 $disp->load(@commands);
248         }
249
250         %{$self->targets} = ();
251         my $channels = new IO::File 'channels' or die $!;;
252         while (<$channels>){
253                 my ($chan, @types) = split /\s+/;
254                 say "$chan - @types";
255                 if ($chan =~ /^(.*):(.*)$/){
256                         $chan = $1;
257                         $self->targets->{$2} = [] unless exists $self->targets->{$2};
258                         push @{$self->targets->{$2}},$chan;
259                         say "$2 - @{$self->targets->{$2}}";
260                 }
261                 $disp->add_channel($chan,\@types);
262         }
263
264         return $disp;
265 }
266
267 sub sig_DIE {
268         my( $kernel,$sig, $ex ) = @_[ KERNEL,ARG0, ARG1 ];
269         say "DIED!!!!!!!!!!!!!!";
270         # $sig is 'DIE'
271         # $ex is the exception hash
272         warn "$$: error in event: $ex->{error_str}";
273         $kernel->sig_handled();
274
275         # Send the signal to session that sent the original event.
276         #if( $ex->{source_session} ne $_[SESSION] ) {
277         #$kernel->signal( $ex->{source_session}, 'DIE', $sig, $ex );
278         #}
279 }
280
281 sub signal_handler {
282         my ($kernel, $signal_name, $heap) = @_[KERNEL, ARG0, HEAP];
283         print "First session caught SIG$signal_name\n";
284
285         given($signal_name){
286                 when ('INT') {
287                         exit unless $heap->{irc}->connected;
288                         $heap->{INT} = 1;
289                         $heap->{irc}->yield(quit => 'Bye!');
290                         $kernel->sig_handled();
291                 }
292         }
293         #$kernel->sig_handled();
294 }
295
296 sub irc_disconnected {
297         my ($sender,$heap) = @_[SENDER,HEAP];
298
299         exit if $heap->{INT};
300 }
301
302 sub irc_001 {
303         my ($self,$sender,$kernel) = @_[OBJECT,SENDER,KERNEL];
304
305         # Since this is an irc_* event, we can get the component's object by
306         # accessing the heap of the sender. Then we register and connect to the
307         # specified server.
308         my $irc = $sender->get_heap();
309
310         print "Connected to ", $irc->server_name(), "\n";
311
312         $kernel->yield( 'auth' );
313         $irc->yield( mode => $irc->nick_name, '+ix');
314
315         # we join our channels
316         $irc->yield( join => $_ ) for grep /^#/, keys %{$self->disp->channels};
317         return;
318 }
319
320 sub irc_invite {
321         my ($self,$sender, $who, $channel) = @_[OBJECT,SENDER, ARG0 .. ARG1];
322         my $irc = $sender->get_heap();
323
324         $irc->yield( join => $_ ) for grep /^$channel$/i, keys %{$self->disp->channels}
325 }
326
327 sub irc_public {
328         $_[ARG2] = irc_to_utf8 $_[ARG2];
329 }
330
331 sub irc_msg {
332         $_[ARG2] = irc_to_utf8 $_[ARG2];
333 }
334
335 sub  refresh {
336 }
337
338 sub irc_join {
339 }
340
341 sub discord_ready {
342         my $self = shift;
343         my $hash = shift;
344         $self->discord_id($hash->{user}{id});
345         $self->discord_name($hash->{user}{username});
346
347         say localtime(time) . " - Connected to Discord. $self->{discord_id}";
348 }
349
350 sub discord_message_create {
351 }
352
353 sub discord_guild_create {
354         my $self = shift;
355         my $hash = shift;
356
357         for my $chan (@{$hash->{channels}}) {
358                 say localtime(time) . " - $chan->{id} - $chan->{name}";
359                 $self->discord_channels->{$chan->{id}} = $chan;
360         }
361 }
362
363 sub discord_channel_create {
364         my $self = shift;
365         my $chan = shift;
366
367         for my $key (keys %{$chan}) {
368                 say localtime(time) . " - $key - $chan->{$key}";
369         }
370         $self->discord_channels->{$chan->{id}} = $chan;
371 }
372
373 sub handleCommand {
374         my ($self, $c, $msg) = @_;
375
376         my ($p,$command,$args) = ($msg =~ /^([.])(\S+)(?: (.+))?/);
377
378         if ($msg =~ m{https?://[\w.]+/.+?scan(_id|_grp)?=(\w+)}){
379                 if (!$command || $command =~ m{^https?://}){
380                         ($p,$command,$args) = ('.','addscan',$msg);
381                 }elsif($command ne 'addscan'){
382                         $self->handleCommand ($c, ".addscan $msg")
383                 }
384         }
385
386         return 0 unless $self->disp->has_command($command,$c->channel);
387
388         say localtime(time) . " - $msg";
389
390         return $self->disp->run_command($c,$command,$args);
391 }
392
393 sub parseCommand {
394         my ($self, $msg, $server, $nick, $address, $channel, $model) = @_;
395
396         return if $channel !~ /^#/ && $msg =~ /^~/;
397         $msg = ".$msg"  if $channel !~ /^#/ && $msg =~ /^[^.!]/;
398
399         my ($p,$command,$args) = ($msg =~ /^([.!~])(\S+)(?: (.+))?/);
400
401         if ($msg =~ m{https?://[\w.]+/.+?scan(_id|_grp)?=(\w+)}){
402                 if (!$command || $command =~ m{^https?://}){
403                         ($p,$command,$args) = ('.','addscan',$msg);
404                 }elsif($command ne 'addscan'){
405                         $self->parseCommand (".addscan $msg", $server, $nick, $address, $channel, $model)
406                 }
407         }
408
409         return 0 unless $self->disp->has_command($command,$channel);
410
411         my $reply_string;
412         given ($p){
413                 when ('!'){
414                         $reply_string = "privmsg $nick";
415                 }
416                 when ('~'){
417                         $reply_string = "privmsg $channel";
418                 }
419                 default {
420                         $reply_string = "notice $nick";
421                 }
422         }
423
424         $address =~ s/.*@(.*)/$1/;
425         my $c = NDIRC::IrcContext->new({
426                         host => $address,
427                         nick => $nick,
428                         channel => $channel,
429                         disp => $self->disp,
430                         model => $model,
431                         server => $server,
432                         bot => $self,
433                         reply_string => $reply_string,
434                 });
435
436         return $self->disp->run_command($c,$command,$args);
437 }
438
439 sub toTarget {
440         my ($self, $target, $msg) = @_;
441
442         return unless exists $self->targets->{$target};
443
444         $self->message($msg, @{$self->targets->{$target}});
445
446 }
447
448 sub message {
449         my ($self, $msg, @targets) = @_;
450
451         for (@targets) {
452                 when (/^D-(\d+)$/) {
453                         $self->discordMessage($1, $msg);
454                 }
455                 default {
456                         $self->ircMessage(privmsg => $_, $msg);
457                 }
458         }
459 }
460
461 sub ircMessage {
462         my ($self, $command, $target, $msg) = @_;
463
464         $msg =~ s`<b>(.*?)</b>`${\(chr(2))}$1${\(chr(15))}`gi;
465         $msg =~ s`<c(\d+)>(.*?)</c>`${\(chr(3))}$1$2${\(chr(15))}`gi;
466
467         $self->irc->yield($command, $target, $msg);
468 }
469
470 sub discordMessage {
471         my ($self, $target, $msg) = @_;
472
473         $msg =~ s`<b>(.*?)</b>`**$1**`gi;
474         $msg =~ s`<c(\d+)>(.*?)</c>`*$2*`gi;
475
476         $self->discord->send_message($target, $msg );
477 }
478
479 1;