#!/opt/bin/perl use strict; package markov; sub new { my ($class, @arg) = @_; bless { longest => 10, s_beg => [], # arbitarry "unique" start symbol s_end => [], # arbitrary "unique" end symbol tree => {}, @arg, }, $class; } sub simplify { local $_ = lc shift; y/aeiouüöä//d; $_; } sub seed { my ($self, $symbols) = @_; my @sym = (@$symbols, $self->{s_end}); my @seq = $self->{s_beg}; my $tree = $self->{tree}; while () { my $next = shift @sym or last; shift @seq while @seq > $self->{longest}; for (1 .. @seq) { my $node = $tree->{simplify join "\0", @seq[-$_ .. -1]} ||= {}; $node->{$next}++; $node->{""}++; } push @seq, $next; } } sub complete { my ($self, $symbols, $prob) = @_; my $tree = $self->{tree}; my @res; my @sym = @$symbols; # find starting sequence shift @sym while @sym && !$tree->{simplify join "\0", @sym}; @res = @sym; @sym = $self->{s_beg} unless @sym; #use Dumpvalue; Dumpvalue->new (compactDump => 1, veryCompact => 1, quoteHighBit => 1, tick => '"')->dumpValue ($self); outer: while () { my $node = $tree->{simplify join "\0", @sym}; if ($node) { my $sel = rand $node->{""}; keys %$node; while (my ($k, $v) = each %$node) { if (length $k and ($sel -= $v) < 0) { last outer if $k eq $self->{s_end}; push @sym, $k; push @res, $k; $prob->{$k} = $v / $node->{""}; next outer; } } die "FATAL: internal error"; } else { shift @sym; @sym or die "FATAL: empty prefix (ENOTUNDERSTOOD)"; } } @res } package main; use Socket; use IO::Socket::INET; use YAML; use Encode; use Event; use Net::Knuddels; use List::Util; use Time::HiRes; my @CHANNELS = ( 'Flirt', 'Flirt Private', 'Singles 11-14', 'Singles 15-17', 'Singles 11-14 2', 'Singles 15-17 2', 'Flirt 2', 'Flirt Private 2', 'Singles 11-14 3', 'Singles 15-17 3', 'Flirt 3', 'Flirt Private 3', 'Singles 11-14 4', 'Singles 15-17 4', 'Flirt 4', 'Flirt Private 4', 'Singles 11-14 5', 'Singles 15-17 5', 'Flirt 5', 'Flirt Private 5', 'Singles 11-14 6', 'Singles 15-17 6', 'Flirt 6', 'Flirt Private 6', 'Singles 11-14 7', 'Singles 15-17 7', 'Flirt 7', 'Flirt Private 7', 'Singles 11-14 8', 'Singles 15-17 8', 'Flirt 8', 'Flirt Private 8', ); my $logdir = "logs"; my $Knick = "ich bin suess"; my $Kpass = "qwerty"; my $client; my $seed = Load do { open my $fh, "<", "markovbot.dat"; binmode $fh, ":utf8"; local $/; <$fh> }; $seed ||= []; my $fwd = new markov longest => 4; my $rev = new markov longest => 4; my %freq; my $word_cnt; sub word { $_[0] =~ /(\w+)/ ? lc $1 : (); } sub seed_msg { my $msg = $_[0]; return if $msg =~ /leck|fick|möse|mose|moese|uschi|usci|ushi|fikk|scheide|vagina |bums|wichs|wix|popp|popen|fuck|dreck|laber|saugen|pussy|\bsuck\b|lutsch |knallen|schlecken |\bblas|piss|schluck|fingern|spritz|\bloch|\bsteck|vögel|voegel|vogel |hure|strich|sklave|handschell|slave|perver|befehl|stöhn|dildo |stengel|penis|schwanz|pimmel|steifen|ejak|sperma|\btitt|\bass|\bsack|\bsaft\b|\beier\b|willy |\bcs\b|\bts\b|\brs\b|\bicq\b |cam\b|\bmsn\b|\bbot\b|\bchat.*bot\b|tanga|schlafen|sex|\bsau\b |intim|arsch|fotze|dumm|schnauze|klappe|rasiert|fresse|maul\b|\bmaul|rosett?e |zieh.*aus|nackt|geile|feucht|willig |sätz|saetz|setze|\bsatz |sc?h?wul|schwuchtel|transe|transv|schlampe|tussi|nutte|mädel |admin|nachgeburt |(?-i:[A-Z]{4,}) |\S{20,} /xi; #my @msg = $msg =~ m/(\S+)/g; my @msg = split /\b/, $msg; $freq{word $_}++ for @msg; $word_cnt += @msg; $fwd->seed (\@msg); $rev->seed ([reverse @msg]); } for (@$seed) { seed_msg $_; } my %grammar_reply = qw( ich du du ich mir dir dir mir mein dein dein mein deine meine meine deine deiner meiner meiner deiner frau mann mädel junge mädchen junge girls boys boys girls huhu hi hi hi hallo hi typen mädels bye bye ciao bye ); sub gen_reply { my ($msg) = @_; my @msg = $msg =~ /(\S+)/g; my @key = map { my $word = word $_; $freq{$word} < $word_cnt * 0.0003 && $freq{$word} && 2 <= length $word ? $word : () } @msg; my @srch = ("", @key, map { my $word = word $_; $grammar_reply{$word} || () } @msg); my $reply; my $best = 2; my $idx; for (1..200) { my $prob = {}; my @r = $rev->complete ( [reverse $fwd->complete ( [@srch ? $srch[++$idx % @srch] : ()], $prob ) ], $prob ); my $b = (rand 0.02 / (@r + 1)) + (@key ? (List::Util::sum map $prob->{$_} || 1, @key) / @key : 0); $b += 0.2 if @r < 3; ($reply, $best) = ((join "", reverse @r), $b) if $b < $best; } $reply; } while ($ENV{DEBUG}) { my $r = gen_reply scalar <>; print "$r\n\n"; } Event->signal (signal => "INT", cb => sub { Event::unloop(-1) }); Event->timer (interval => 60, cb => sub { open my $fh, ">", "markovbot.dat~" or return; binmode $fh; print $fh Encode::encode_utf8 Dump $seed; close $fh; rename "markovbot.dat~", "markovbot.dat"; }); sub logit { my ($msg, $file, $src, $dst, $room) = @_; mkdir $logdir; my $fh; unless (open $fh, ">>:utf8", Encode::encode_utf8 "$logdir/$file") { warn "Couldn't open for appending $logdir/$src: $!\n"; return; } print $fh "$room\t$src\t$dst\t$msg\n"; } #################################################################################### ########################## MAIN START ############################################## #################################################################################### $client = new Net::Knuddels::Client PeerAddr => "213.61.5.150:2710", command_wait => sub { my ($client, $wait) = @_; Event->timer (after => $wait, cb => sub { $client->command_cb }); }; Event->io ( fd => $client->fh, poll => 'r', cb => sub { $client->ready or $_[0]->w->cancel; }); $client->login; $client->register (dialog => sub { use Dumpvalue; print "---\n"; Dumpvalue->new (compactDump => 1, veryCompact => 1, quoteHighBit => 1, tick => '"')->dumpValue ([@_]); Event::unloop(-1) if grep /Falsches.*Passwort/i, @_; }); $client->register (login => sub { $client->enter_room ($_, $Knick, $Kpass) for @CHANNELS; }); $client->register (msg_room => sub { my ($room, $user, $msg) = @_; }); my @queue; Event->timer (interval => 1, cb => sub { my $msg = shift @queue or return; logit ($msg->[2], $msg->[0], $Knick, $msg->[0], $msg->[1]); $client->send_priv_msg (@$msg); }); my $some_room; $client->register (room_info => sub { print "JOIN ROOM: $_[0]\n"; $some_room = $_[0]; }); Event->timer (after => 60, interval => 60, cb => sub { $client->send_priv_msg ("James", $some_room, "/knuschel"); }); my %next_time; $client->register (msg_priv_nondup => sub { my ($room, $src, $dst, $msg) = @_; my $NOW = Time::HiRes::time; $msg =~ s/\260[^\260]*\260//g; print "($room) $src >> $msg\n"; logit ($msg, $src, $src, $dst, $room); return if $next_time{$src} > time; # do not talk unnaturally often my $reply = gen_reply $msg; push @$seed, $msg; seed_msg $msg; my $delay = 30 * (rand) ** 5 + 0.3 * length $reply; $next_time{$src} = time + $delay; print "($room) $src << $reply ($delay)\n"; Event->timer (at => $NOW + $delay, cb => sub { push @queue, [$src, $room, $reply]; }); }); Event::loop;