- add translations
authorAndreas Scherbaum <andreas@scherbaum.biz>
Tue, 17 Jan 2012 00:48:01 +0000 (01:48 +0100)
committerAndreas Scherbaum <andreas@scherbaum.biz>
Tue, 17 Jan 2012 00:48:01 +0000 (01:48 +0100)
- add per channel translations
- add translated error messages
- add command handling

docbot.conf
docbot.pl

index 83c66aa0dffcf877b438182c0e23bb7ecb65f5d8..722632fd3733bded6c1e75116edb4ed21eb9e51c 100644 (file)
@@ -3,7 +3,7 @@ channels:
   '#pg_docbot':
     password: ''
     session: 1
-    language: 'en'
+    language: 'de'
   '#pg_docbot_test1':
     password: ''
     session: 2
@@ -48,4 +48,30 @@ bot:
   casesearch: 0
 translations:
   de:
-    learn: lerne
+    learn: 'lerne|lernen'
+    forget: 'vergesse|vergessen'
+    config: 'konfiguriere'
+    help: 'hilfe'
+    successfully_added_1_keyword: 'Ein Schlüsselwort hinzugefügt'
+    successfully_added_1_url: 'Eine URL hinzugefügt'
+    successfully_added_x_keyword: '% Schlüsselwörter hinzugefügt'
+    successfully_added_x_url: 'X URLs hinzugefügt'
+    successfully_deleted_1_keyword: 'Ein Schlüsselwort gelöscht'
+    successfully_deleted_1_url: 'Eine URL gelöscht'
+    successfully_deleted_x_keyword: '% Schlüsselwörter gelöscht'
+    successfully_deleted_x_url: '% URLs gelöscht'
+    url_not_found: 'URL nicht gefunden'
+    urls_not_found: 'URLs nicht gefunden'
+    keyword_not_found: 'Schlüsselwort nicht gefunden'
+    keywords_not_found: 'Schlüsselwörter nicht gefunden'
+    nothing_found: 'Nichts gefunden'
+    additional_information_at: 'Weitere Informationen unter'
+    you_are_not_authorized: 'Sie sind nicht authorisiert'
+  fr:
+    learn: 'apprendre'
+    forget: 'oublier'
+    help: 'aide'
+  es:
+    learn: 'aprender'
+    forget: 'olvidar'
+    help: 'ayuda'
index 7014c51cd0d79b246dcd70b93d86983198dadc43..004b09d7605559f217a76968b8bcafe029d48ff5 100755 (executable)
--- a/docbot.pl
+++ b/docbot.pl
@@ -23,6 +23,8 @@ require("./db.pm");
 package main;
 use strict;
 use warnings;
+#sub POE::Kernel::TRACE_EVENTS  () { 1 }
+#sub POE::Kernel::TRACE_SIGNALS  () { 1 }
 use POE;
 use POE qw(Component::IRC Component::IRC::Plugin::Connector);
 use POE::Component::IRC;
@@ -58,8 +60,6 @@ $main::loglevel = DEBUG;
 
 
 
-# list of joined irc channels
-%main::irc_channels = ();
 # list of sessions
 %main::sessions = ();
 # logfile name
@@ -157,8 +157,12 @@ foreach my $session (keys(%main::sessions)) {
             _default         => \&_default,
             irc_001          => \&on_connect,
             irc_public       => \&on_message,
+            irc_msg          => \&on_message,
+            public2          => \&on_message,
             irc_376          => \&on_end_motd,
             irc_433          => \&on_nickused,
+            irc_330          => \&on_whois_identified,
+            irc_318          => \&on_whois_end,
             irc_ping         => \&on_ping,
             autoping         => \&do_autoping,
             irc_error        => \&on_error,
@@ -170,7 +174,7 @@ foreach my $session (keys(%main::sessions)) {
 
 #    inline_states => {
 #        irc_353          => \&on_names,
-#        irc_join         => \&on_join,
+#            irc_join         => \&on_join,
 #        irc_part         => \&on_part,
 #        irc_quit         => \&on_quit,
 #        irc_nick         => \&on_nick,
@@ -240,7 +244,7 @@ exit 0;
 
 # init_config()
 #
-# init config
+# init configuration
 #
 # parameter:
 #  none
@@ -255,7 +259,7 @@ sub init_config {
 
 # read_config()
 #
-# read config & set variables accordingly
+# read configuration & set variables accordingly
 #
 # parameter:
 #  - config file name
@@ -272,7 +276,7 @@ sub read_config {
 
 # config_get_key1()
 #
-# read config value
+# read configuration value
 #
 # parameter:
 #  - config key 1
@@ -287,7 +291,7 @@ sub config_get_key1 {
 
 # config_get_key2()
 #
-# read config value
+# read configuration value
 #
 # parameter:
 #  - config key 1
@@ -304,7 +308,7 @@ sub config_get_key2 {
 
 # config_get_key3()
 #
-# read config value
+# read configuration value
 #
 # parameter:
 #  - config key 1
@@ -323,7 +327,7 @@ sub config_get_key3 {
 
 # config_get_keys1()
 #
-# read config keys
+# read configuration keys
 #
 # parameter:
 #  - config key 1
@@ -338,7 +342,7 @@ sub config_get_keys1 {
 
 # validate_config()
 #
-# read config & validate important settings
+# read configuration & validate important settings
 #
 # parameter:
 #  none
@@ -392,7 +396,7 @@ sub validate_config {
 
 # reread_config()
 #
-# reread config & execute changes
+# reread configuration & execute changes
 #
 # parameter:
 #  none
@@ -641,25 +645,24 @@ sub init_sessions {
         $main::sessions{$session}{'last_nick_change_attempt'} = time();
     }
 
+    # FIXME: use channels from config directly on join
     sort_irc_channels_into_sessions();
 }
 
 
-
 sub sort_irc_channels_into_sessions {
-    my @channels = config_get_keys1('channels');
+    my @channels = $main::config->config_get_keys1('channels');
 
     foreach my $channel (@channels) {
         # FIXME: find out if this channel is already joined
         # FIXME: if session changes, reassign the channel
-        my $session = config_get_key3('channels', $channel, 'session');
+        my $session = $main::config->config_get_key3('channels', $channel, 'session');
         push(@{$main::sessions{$session}{'irc_channels'}}, $channel);
         print_msg("assign irc channel '$channel' to session '$session'", DEBUG);
     }
 }
 
 
-
 sub write_to_channel {
 
 }
@@ -798,7 +801,7 @@ sub death {
     }
 
     # have to shutdown here, else Component::IRC Component::IRC::Plugin::Connector will reconnect
-    $poe_kernel->delay_add( execute_shutdown => 10 );
+    $poe_kernel->delay_add( execute_shutdown => 5 );
     $shutdown = 1;
 
     return;
@@ -910,6 +913,262 @@ sub read_session_activity {
 }
 
 
+# find_command()
+#
+# find a valid command in a message line
+#
+# parameter:
+#  - message line
+#  - optional: channel name
+# return:
+#  - array with:
+#    - command
+#    - rest of string
+#  undef if no command could be identified
+sub find_command {
+    my $msg = shift;
+    my $channel = shift;
+
+    my ($command, $string);
+
+    if ($msg =~ /^\s*\?([a-z]+)\s+(.+)/) {
+        $command = lc($1);
+        $string = $2;
+
+        # looks like a command, at least started with a question mark
+        # find out if it really is one
+
+        if (is_valid_command($command)) {
+            return ($command, $string);
+        }
+
+        # try to translate the command
+        # find the channel language
+        my $channel_language = config_get_key3('channels', $channel, 'language');
+        # not defined channel language leads to a full search across all languages
+        # this is just fine
+
+        my $translation = find_translation($channel_language, $command);
+        if (defined($translation) and is_valid_command($translation)) {
+            return ($translation, $string);
+        }
+    }
+
+    return undef;
+}
+
+
+# is_valid_command()
+#
+# find out if this is a valid command (includes valid admin commands)
+#
+# parameter:
+#  - command
+# return:
+#  - 0/1
+sub is_valid_command {
+    my $command = shift;
+
+    my $status = is_valid_admin_command($command);
+    if ($status == 1) {
+        return 1;
+    }
+
+    if ($command eq 'help') {
+        return 1;
+    } elsif ($command eq 'info') {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+# is_valid_admin_command()
+#
+# find out if this is a valid admin command
+#
+# parameter:
+#  - command
+# return:
+#  - 0/1
+sub is_valid_admin_command {
+    my $command = shift;
+
+    if ($command eq 'learn') {
+        return 1;
+    } elsif ($command eq 'forget') {
+        return 1;
+    } elsif ($command eq 'config') {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+# translate_with_default()
+#
+# translate a text into another language
+#
+# parameter:
+#  - language
+#  - text to translate
+#  - default translation
+# return:
+#  - translated text, or returns the original text if there is no translation
+sub translate_with_default {
+    my $language = shift;
+    my $word = shift;
+    my $default = shift;
+
+    my $translation = translate($language, $word);
+    if (!defined($translation)) {
+        return $default;
+    }
+
+    return $translation;
+}
+
+
+# translate()
+#
+# translate a text into another language
+#
+# parameter:
+#  - language
+#  - text to translate
+# return:
+#  - translated text, or undef
+sub translate {
+    my $language = shift;
+    my $word = shift;
+
+
+    my $translation = config_get_key3('translations', $language, $word);
+    if (!defined($translation)) {
+        return undef;
+    }
+
+    # return the first word, if multiple translations are available
+    if ($translation =~ /^([^\|]+)\|/) {
+        $translation = $1;
+    }
+
+    return $translation;
+}
+
+
+# translations()
+#
+# returns all translations for a text into another language
+#
+# parameter:
+#  - language
+#  - text to translate
+# return:
+#  - array with translations, or undef
+sub translations {
+    my $language = shift;
+    my $word = shift;
+
+    my $translation = config_get_key3('translations', $language, $word);
+    if (!defined($translation)) {
+        return undef;
+    }
+
+    # always return all translations in an array, even if there is only one
+    my @translation = ($translation);
+    if ($translation =~ /\|/) {
+        @translation = split(/\|/, $translation);
+    }
+
+    return @translation;
+}
+
+
+# find_translation()
+#
+# find a translated word and return the according key
+#
+# parameter:
+#  - language
+#  - translated text
+#  - optional: flag if case sensitive search, default is not case sensitive, 0 will search sensitive
+# return:
+#  - translation key, or undef
+sub find_translation {
+    my $language = shift;
+    my $word = shift;
+    my $lowercase = 1;
+    if (defined($_[0])) {
+        $lowercase = shift;
+    }
+
+    my @languages = $main::config->config_get_keys1('translations');
+
+    # find a word in one or all languages
+    foreach my $tmp (@languages) {
+        if (!defined($language) or (defined($language) and $language eq $tmp)) {
+            my @tmp2 = $main::config->config_get_keys2('translations', $tmp);
+            foreach my $tmp2 (@tmp2) {
+                my $tmp3 = $main::config->config_get_key3('translations', $tmp, $tmp2);
+                my @tmp3 = ($tmp3);
+                if ($tmp3 =~ /\|/) {
+                    @tmp3 = split(/\|/, $tmp3);
+                }
+                foreach my $tmp4 (@tmp3) {
+                    if ($lowercase) {
+                        if (lc($word) eq lc($tmp4)) {
+                            return $tmp2;
+                        }
+                    } else {
+                        if ($word eq $tmp4) {
+                            return $tmp2;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return undef;
+}
+
+
+# translate_text_for_channel()
+#
+# translate a text for a channel
+#
+# parameter:
+#  - channel name
+#  - text key
+#  - default text if no translation is available (not optional!)
+# return:
+#  - translated text (or default text)
+sub translate_text_for_channel {
+    my $channel = shift;
+    my $text_key = shift;
+    my $default_text = shift;
+
+    my $text = $default_text;
+
+    # translate text
+    if (substr($channel, 0, 1) eq '#') {
+        my $channel_language = config_get_key3('channels', $channel, 'language');
+        if (defined($channel_language)) {
+            $text = translate_with_default($channel_language, $text_key, $default_text);
+        }
+    }
+
+    return $text;
+}
+
+
+
+
+
+
 
 ######################################################################
 # IRC functions
@@ -938,6 +1197,9 @@ sub on_start {
 
     $irc->yield( connect => { Debug => $main::debug_traffic } );
 
+    # enable tracing
+    #$_[SESSION]->option(trace => 1);
+
     return;
 }
 
@@ -969,8 +1231,10 @@ sub on_connect {
 
     set_session_activity($session);
 
+    $main::sessions{$session}{'irc_channels'} = [];
     # join all channels
     foreach my $channel (@irc_channels) {
+        # based on the current configuration, each channel can only be joined by one bot session
         $irc->yield( join => $channel );
     }
 
@@ -978,23 +1242,230 @@ sub on_connect {
 }
 
 
-# irc_public
+########
+# on_message ( )
+####
+# called when some message was sent to channel or to bot
+#
+
+# on_message()
+#
+# called when some message was sent to channel or to bot
+#
 sub on_message {
-    my ($sender, $who, $where, $what) = @_[SENDER, ARG0 .. ARG2];
+    my ( $kernel, $heap, $who, $where, $msg, $sender ) = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2, SENDER ];
     my $nick = ( split /!/, $who )[0];
     my $channel = $where->[0];
+    my $replyto = $channel;
+    my $full_msg = $msg;
+    my $like = 0;
+    print_msg("on_message($msg)", DEBUG);
 
     my $irc = $sender->get_heap();
     my $session = find_irc_session($irc);
-    #print "\n\nsession: $session\n\n";
-    set_session_activity($session);
 
 
-    if ( my ($rot13) = $what =~ /^rot13 (.+)/ ) {
-        $rot13 =~ tr[a-zA-Z][n-za-mN-ZA-M];
-        $irc->yield( privmsg => $channel => "$nick: $rot13" );
+    # recognize valid command (both admin and unprivileged)
+    my ($command, $string) = find_command($msg, (substr($channel, 0, 1) eq '#') ? $channel : undef);
+
+
+    if (defined($command)) {
+        my $answer;
+
+        # handle all admin commands
+        if (is_valid_admin_command($command)) {
+
+            # no authentication information available, create callback
+            if (!defined($heap->{whois_callback}->{$nick}->{authed})) {
+                $heap->{whois_callback}->{$nick} = {event => (lc($channel) eq lc($irc->nick_name())) ? 'irc_msg' : 'irc_public'};
+                $heap->{whois_callback}->{$nick}->{authed} = 0;
+                # register a callback
+                @{$heap->{whois_callback}->{$nick}->{args}} = ($who, $where, $msg);
+
+                # no auth information available
+                # shoot a 'whois' to the irc server
+                $irc->yield( whois => $nick );
+                return;
+            }
+            # handle authentication callback
+            elsif ($heap->{whois_callback}->{$nick}->{authed} != 1) {
+                # user is not logged in on freenode
+                $answer = "You are not authorized";
+                # translate error message
+                $answer = translate_text_for_channel($channel, 'you_are_not_authorized', $answer);
+            }
+            else {
+                # execute desired command
+                #$answer = $admin_commands->{$command}($kernel, $nick, $channel, $string);
+                $answer = "Execute command: $command";
+                print_msg("Execute command: $command", INFO);
+            }
+            # drop the callback for this nick
+            undef ($heap->{whois_callback}->{$nick});
+
+            if (length($answer)) {
+                # if command was called in channel print answer to channel, if it was PM print it as PM
+                if (lc($channel) eq lc($irc->nick_name())) {
+                    $irc->yield( privmsg => $nick, $answer);
+                }
+                else {
+                    $irc->yield( privmsg => $channel, $answer);
+                }
+                return;
+            }
+
+
+
+
+        }
+    }
+
+
+#    if ($msg =~ /^\s*(\?(?:learn|forget|config))\s+(.+)/i) {
+#        my ($command, $string) = ($1, $2);
+#        my $answer;
+#
+#        if (!defined($heap->{whois_callback}->{$nick}->{authed})) {
+#            $heap->{whois_callback}->{$nick} = {event => (lc($channel) eq lc($irc->nick_name())) ? 'irc_msg' : 'irc_public'};
+#            $heap->{whois_callback}->{$nick}->{authed} = 0;
+#            # register a callback
+#            @{$heap->{whois_callback}->{$nick}->{args}} = ($who, $where, $msg);
+#
+#            # no auth information available
+#            # shoot a 'whois' to the irc server
+#            $irc->yield( whois => $nick );
+#            return;
+#        }
+#        elsif ($heap->{whois_callback}->{$nick}->{authed} != 1) {
+#            $answer = "You are not authorized";
+#        }
+#        else {
+#            # execute desired command
+#            #$answer = $admin_commands->{$command}($kernel, $nick, $channel, $string);
+#            $answer = "Execute command: $command";
+#            print_msg("Execute command: $command", INFO);
+#        }
+#        # drop the callback for this nick
+#        undef ($heap->{whois_callback}->{$nick});
+#
+#        if (length($answer)) {
+#            # if command was called in channel print answer to channel, if it was PM print it as PM
+#            if (lc($channel) eq lc($irc->nick_name())) {
+#                $irc->yield( privmsg => $nick, $answer);
+#            }
+#            else {
+#                $irc->yield( privmsg => $channel, $answer);
+#            }
+#            return;
+#        }
+#    }
+#    elsif ($msg =~ /^\s*\?\?(\?*)(.+)/i) {
+#        $like = ($1 and $LIKESEARCH) ? 1 : 0;
+#        $msg = $2;
+#        $msg =~ s/^\s+//;
+#
+#        if (substr($channel, 0, 1) eq '#')
+#        {
+#            if ($my_nick ne $irc_nick) {
+#                if (grep( /^$channel$/i, find_nick( $heap, $irc_nick ) )) {
+#                    print_msg("Not processing query command, master bot is on channel", DEBUG);
+#                    return;
+#                }
+#            }
+#        }
+#
+#        if ($msg =~ /^(.+)\s+>\s+(\w+)/i) {
+#            return unless (grep( /^$channel$/i, find_nick( $heap, $2 ) ));
+#            $replyto = $2;
+#            $msg = $1;
+#        } elsif (lc($channel) eq lc($my_nick)) {
+#            $replyto = $nick;
+#        } else {
+#            $replyto = $channel;
+#        }
+#    }
+#    elsif ($msg =~ /^\s*$my_nick\W+tell\s+(\w+)\s+about\s+(.+)/i) {
+#        if ($1 eq "me") {
+#            $replyto = $nick;
+#        } elsif ($1 eq "us") {
+#            $replyto = $channel;
+#        } else {
+#            $replyto = $1;
+#            return unless (grep( /^$channel$/i, find_nick( $heap, $replyto ) ));
+#        }
+#
+#        $msg = $2;
+#    } else {
+#        return;
+#    }
+#
+#    # get data from db
+#    my $answers = get_answer($msg, $like);
+#    my $message_to_say = get_message($msg);
+#    my $numanswers = @$answers;
+#
+#    # print each answer as one line, except when there are more than $MAXWRAP
+#    if ($numanswers) {
+#        $kernel->post(pg_docbot => privmsg => $replyto, "For information about '$msg' see:\n");
+#        if ($numanswers <= $MAXWRAP) {
+#            for my $answer (@$answers) {
+#                $kernel->post(pg_docbot => privmsg => $replyto, $answer)
+#            }
+#        }
+#        else {
+#            for my $answer (word_wrap(' :: ', 250, @$answers)) {
+#                $kernel->post(pg_docbot => privmsg => $replyto, $answer);
+#            }
+#        }
+#    }
+#    else { # "nothing found," is always sends to the caller, not to the receiver
+#        # if command was called in channel print, answer to channel, if it was PM print it as PM
+#        if (lc($channel) eq lc($my_nick)) {
+#            $kernel->post(pg_docbot => privmsg => $nick, "Nothing found");
+#        } else {
+#            $kernel->post(pg_docbot => privmsg => $channel, "Nothing found");
+#        }
+#    }
+}
+
+
+
+
+
+# on_whois_identified()
+#
+# parse whois lines
+#
+sub on_whois_identified {
+    my ( $kernel, $heap, $detail, $sender ) = @_[KERNEL, HEAP, ARG1, SENDER];
+    my $nick = ( split / /, $detail )[0];
+
+    my $irc = $sender->get_heap();
+    my $session = find_irc_session($irc);
+    print_msg("on_whois_identified(session: $session, nick: $nick)\n", DEBUG);
+
+    if (defined($heap->{whois_callback}->{$nick})) {
+        print_msg("Nick $nick is authed", DEBUG);
+        $heap->{whois_callback}->{$nick}->{authed} = 1;
+    }
+}
+
+
+# on_whois_end()
+#
+# end of whois output
+#
+sub on_whois_end {
+    my ( $kernel, $heap, $detail, $sender ) = @_[KERNEL, HEAP, ARG1, SENDER];
+    my $nick = ( split / /, $detail )[0];
+
+    my $irc = $sender->get_heap();
+    my $session = find_irc_session($irc);
+    print_msg("on_whois_end(session: $session, nick: $nick)\n", DEBUG);
+
+    if (defined($heap->{whois_callback}->{$nick}->{event})) {
+        $irc->send_event($heap->{whois_callback}->{$nick}->{event} => @{$heap->{whois_callback}->{$nick}->{args}});
     }
-    return;
 }
 
 
@@ -1229,5 +1700,6 @@ sub on_error {
 
 
 
+
 #
 ## vi: ts=4