# HG changeset patch # User Dominic Cleal # Date 1228574938 0 # Node ID dddb8424103279dd1dbc9eadf25557d79a160a34 # Parent 5cba712b27aa538b4c7c0aa99ce954d494546256 Bot can now read skills from the API for its friends diff -r 5cba712b27aa -r dddb84241032 skillbot.pl --- a/skillbot.pl Sat Dec 06 14:40:54 2008 +0000 +++ b/skillbot.pl Sat Dec 06 14:48:58 2008 +0000 @@ -4,22 +4,23 @@ =pod -twitfolk +skillbot -Gate tweets from your Twitter friends into an IRC channel. Currently can be -found on irc://irc.bitfolk.com/bitfolk as the user "Twitfolk". +Provides notifications of EVE Online skill completions over IRC. + +Based on Andy Smith's twitfolk bot: +$Id: twitfolk.pl 802 2008-11-29 00:07:38Z andy $ Copyright ©2008 Andy Smith +Portions copyright ©2008 Dominic Cleal Artistic license same as Perl. - -$Id: twitfolk.pl 802 2008-11-29 00:07:38Z andy $ =cut use strict; use warnings; -use Net::Twitter; +use WebService::EveOnline; use Data::Dumper; use Net::IRC; use HTML::Entities; @@ -33,9 +34,7 @@ my %friends; -my $last_tweet = 0; - -open(CONFIG, "< twitfolk.conf") or die "can't open twitfolk.conf for reading: $!"; +open(CONFIG, "< skillbot.conf") or die "can't open skillbot.conf for reading: $!"; while() { chomp; s/#.*//; @@ -45,10 +44,10 @@ my ($var, $value) = split(/\s*=\s*/, $_, 2); $config{$var} = $value; } -close(CONFIG) or die "can't close twitfolk.conf: $!"; +close(CONFIG) or die "can't close skillbot.conf: $!"; -my $version = '0.00001'; -my $ircname = "twitfolk v$version"; +my $version = '0.01'; +my $ircname = "skillbot v$version"; my $DEBUG = $ENV{'IRC_DEBUG'} || 0; my $time_to_die = 0; @@ -60,19 +59,8 @@ print PIDFILE "$$\n"; close(PIDFILE) or die "can't close $config{'pidfile'}: $!"; -my $twit = Net::Twitter->new(username => $config{twitter_user}, - password => $config{twitter_pass}); - -die "Can't connect to Twitter: $!" unless $twit; - -#$twit->update(sprintf("Connecting to irc://%s/", $config{target_server})); -#$twit->http_code == 200 or die "Twitter->update: $twit->http_message()"; - -sync_friends(undef); update_friends(undef); -$last_tweet = init_last_tweet(); - my $irc = new Net::IRC; my $conn = $irc->newconn(Server => $config{'target_server'}, @@ -144,16 +132,11 @@ add_repeat_timer(600, sub { my ($timer, $self) = @_; join_channels($self); }); # Read the "friends" config file every 6 minutes and make sure we have - # friended them all + # API sessions for them all add_repeat_timer(360, sub { my ($timer, $self) = @_; update_friends($self); }); - # Ask Twitter who our friends are every hour and make sure they are - # known to us - add_repeat_timer(3600, sub { my ($timer, $self) = @_; sync_friends($self); }); - - # Check for new tweets every 5 minutes. API allows 100 calls every 60 - # minutes so should be okay - add_repeat_timer(300, sub { my ($timer, $self) = @_; check_tweets($self); }); + # Check for new skills every 5 minutes. + add_repeat_timer(300, sub { my ($timer, $self) = @_; check_training($self); }); } sub nickserv_id_now @@ -202,7 +185,7 @@ =cut # Now we're in, check for tweets as a one-off - add_one_shot_timer(10, sub { my ($timer, $self) = @_; check_tweets($self); }); + add_one_shot_timer(10, sub { my ($timer, $self) = @_; check_training($self); }); } } @@ -347,9 +330,9 @@ Read a list of friends from the friends_file. These will be friended in Twitter if they aren't already. Format is: -screen_name IRC_nick +character_name user_id api_key IRC_nick -Start a line with # for a comment. Any kind of white space is okay. +Start a line with # for a comment. Columns must be tab separated. =cut sub update_friends @@ -361,34 +344,35 @@ while () { next if (/^#/); - if (/^(\S+)\s+(\S+)/) { - my $f = lc($1); - my $nick = $2; + if (/^([^\t]+)\t+([0-9]+)\t+([A-F0-9]{64})\t+([^\t]+)/i) { + my $c = $1; + my $uid = $2; + my $key = uc($3); + my $nick = $4; + + if (! $friends{$c}) { + my $api = WebService::EveOnline->new( { user_id => $uid, + api_key => $key } ); - if (! $friends{$f}) { - my $u = $twit->show_user($f); - - if ($twit->http_code != 200) { - irc_debug("twitter->show_user(%s) failed: %s", $f, - $twit->http_message); + foreach my $character ($api->characters) { + next if $c ne $character->name; + $friends{$c}->{char} = $character; + last; + } + + unless (defined $friends{$c}->{char}) + { + irc_debug("EVE: Unable to find character %s for ID %lu", + $c, $uid); next; } - - my $id = $u->{id}; - $friends{$f}->{id} = $id; - - irc_debug("Twitter: Adding new friend '%s' (%lu)", $f, - $id); + + $friends{$c}->{api} = $api; - $twit->create_friend($id); - - if ($twit->http_code != 200) { - irc_debug("twitter-> create_friend($id) failed: %s", - $twit->http_message); - } + irc_debug("EVE: Adding new friend '%s' =~ %s", $c, $nick); } - $friends{$f}->{nick} = $nick; + $friends{$c}->{nick} = $nick; } } @@ -397,216 +381,36 @@ =pod -Learn friends from those already added in Twitter, just in case they got added -from outside as well. Might make this update the friends file at some point. +Check for any characters that aren't known to be training, then call the API +to see if they've started, setting timers. =cut -sub sync_friends +sub check_training { my $self = shift; - my $twitter_friends = $twit->friends({ - id => $config{twitter_id} - }); - - if ($twit->http_code != 200) { - irc_debug("twitter->friends() failed: %s", $twit->http_message); - return; - } - - foreach my $f (@{ $twitter_friends }) { - my $screen_name = lc($f->{screen_name}); - my $id = $f->{id}; - - $friends{$screen_name}->{id} = $id; - - if (! defined $friends{$screen_name}->{nick}) { - $friends{$screen_name}->{nick} = $screen_name; - } - - irc_debug("Twitter: Already following '%s' (%lu)", $screen_name, - $friends{$screen_name}->{id}); - } - -} - -=pod - -Get a friends timeline and announce it to IRC. Only does $max at once and only -requests 10 * $max from Twitter. - -=cut -sub check_tweets -{ - my $self = shift; - my $tweets = undef; - - # Ask for 10 times as many tweets as we will ever say, but no more than - # 200 - my $max = $config{max_tweets} >= 20 ? 200 : $config{max_tweets} * 10; - my $count = 0; - - # Ask for the timeline of friend's statuses, only since the last tweet - # if we know its id - if ($last_tweet != 0) { - $tweets = $twit->friends_timeline({ - since_id => $last_tweet, - count => $max, - }); - } else { - $tweets = $twit->friends_timeline({ - count => $max, - }); - } - - if ($twit->http_code != 200) { - irc_debug("twitter->friend_timelines() failed: %s", - $twit->http_message); - return; - } - -=pod - -$tweets should now be a reference to an array of: + foreach my $f (keys %friends) { + # Skip skills training that we've learnt about + next if defined $friends{$f}->{skill}; - { - 'source' => 'web', - 'favorited' => $VAR1->[0]{'favorited'}, - 'truncated' => $VAR1->[0]{'favorited'}, - 'created_at' => 'Tue Oct 28 22:22:14 +0000 2008', - 'text' => '@deltafan121 Near Luton, which is just outside London.', - 'user' => { - 'location' => 'Bedfordshire, United Kingdom', - 'followers_count' => 10, - 'profile_image_url' => 'http://s3.amazonaws.com/twitter_production/profile_images/62344418/SP_A0089_2_normal.jpg', - 'protected' => $VAR1->[0]{'favorited'}, - 'name' => 'Robert Leverington', - 'url' => 'http://robertleverington.com/', - 'id' => 14450923, - 'description' => '', - 'screen_name' => 'roberthl' - }, - 'in_reply_to_user_id' => 14662919, - 'id' => 979630447, - 'in_reply_to_status_id' => 979535561 - } -=cut - -=pod -But I guess we better check, since this happened one time at band camp: - -Tue Nov 18 07:58:41 2008| *** twitter->friend_timelines() failed: Can't connect to twitter.com:80 (connect: timeout) -Tue Nov 18 08:03:41 2008| *** twitter->friend_timelines() failed: Can't connect to twitter.com:80 (connect: timeout) -Tue Nov 18 08:08:50 2008| *** twitter->friend_timelines() failed: read timeout -Tue Nov 18 08:13:41 2008| *** twitter->friend_timelines() failed: Can't connect to twitter.com:80 (connect: timeout) -Tue Nov 18 08:18:41 2008| *** twitter->friend_timelines() failed: Can't connect to twitter.com:80 (connect: timeout) -Tue Nov 18 08:23:43 2008| *** twitter->friend_timelines() failed: Can't connect to twitter.com:80 (connect: timeout) -Not an ARRAY reference at ./twitfolk.pl line 494. -=cut + my $skill = $friends{$f}->{char}->skill->in_training; + # Nothing training + next unless $skill; - if (ref($tweets) ne "ARRAY") { - irc_debug("twitter->friend_timelines() didn't return an arrayref!"); - return; - } - - irc_debug("Got %u new tweets", scalar @{ $tweets }); - - # Iterate through them all, sorted by id low to high - foreach my $tweet (sort { $a->{id} <=> $b->{id} } @{ $tweets }) { - if ($count >= $config{max_tweets}) { - irc_debug("Already did %u tweets, stopping there", $count); - last; - } - - if (lc($tweet->{user}->{screen_name}) eq 'bitfolk') { - irc_debug("Skipping tweet from myself"); - next; - } + irc_debug("Character %s is training %s (%s)", + $friends{$f}->{char}->name, $skill->name, + $skill->time_remaining); - if ($tweet->{id} <= $last_tweet) { - # Why does Twitter still return tweets that are <= since_id? - irc_debug("Tweet %lu: ignored as somehow <= %lu !?", - $tweet->{id}, $last_tweet); - next; - } - - my $screen_name = lc($tweet->{user}->{screen_name}); - my $text = decode_entities($tweet->{text}); - my $nick; - - if (! exists($friends{$screen_name})) { - irc_debug("I don't have a nickname for Twitter user %s!", - $screen_name); - $nick = $screen_name; - } else { - $nick = $friends{$screen_name}->{nick}; - } - - irc_debug("Tweet %lu: [%s] %s", $tweet->{id}, $screen_name, $text); - + my $text = $skill->name; if ($text =~ /[\n\r]/) { - irc_debug("Tweet %lu contains dangerous characters; removing!", - $tweet->{id}); $text =~ s/[\n\r]/ /g; } - $self->notice('#' . $config{channel}, sprintf("[%s] %s", $nick, + $self->notice('#' . $config{channel}, sprintf("[%s] %s", $friends{$f}->{nick}, encode("utf8", $text))); - - # Save the highest (most recent) id for next time - $last_tweet = $tweet->{id} if ($tweet->{id} > $last_tweet); - $count++; } - - # Save the new id to the last_tweet file if there were any tweets - update_last_tweet($last_tweet) if ($count); } -=pod - -Read the last tweet id from a file so that no tweets should be repeated - -=cut -sub init_last_tweet -{ - return 0 if (! -f $config{tweet_id_file}); - - open(LT, "< $config{tweet_id_file}") or die "Couldn't open tweet_id_file: $!"; - - my $id = 0; - - while () { - if (/^(\d+)/) { - $id = $1; - last; - } else { - die "Weird format $_ in tweet_id_file"; - } - } - - close(LT) or warn "Something weird when closing tweet_id_file: $!"; - - irc_debug("Last tweet id = %lu", $id); - - return $id; -} - -=pod - -Save the id of the most recent tweet so that it won't be repeated should -the bot crash or whatever - -=cut -sub update_last_tweet -{ - my $id = shift; - - open(LT, "> $config{tweet_id_file}") or die "Couldn't open tweet_id_file: $!"; - print LT "$id\n"; - close(LT) or warn "Something weird when closing tweet_id_file: $!"; -} - - END { cleanup_and_die(); }