Twitter IRC Bot

From Nerdhaus
Jump to: navigation, search

Summary

Our TwitterBot lives on our IRC server and allows us to interact with Twitter. It does a couple things:

  1. Tweets from @nerdhaus's friends timeline get sent to our primary channel, #nerdhaus
  2. Tweets from my friends timeline are sent as a private message from TwitterBot to me
  3. I send a private message to TwitterBot and it posts to Twitter for me
  4. In progress: searches the twitter stream for @replies from people I am not following and sends those as a private message too

Using TwitterBot

Tweeting from the IRC Channel

Special:TwitterIRC - Set your Twitter username/password here

You can use TwitterBot to send tweets from the IRC channel! In order to do this, you first need to give it your Twitter username and password. The next trick is to set your username to your first name when you sign in to the IRC server. If you don't, the bot won't find your Twitter username. You can set your username and nick differently if you want.

To post a message to Twitter, send TwitterBot a private message. ANY private message you send to TwitterBot will be tweeted. It gives you a 30-second delay to change your mind in case you accidentally typed in the private message tab. Type !no after accidentally sending it a message and it will cancel the message. (It prompts you for all this too so don't worry about remembering it.)

(To send a private message, type /msg TwitterBot your message while in #nerdhaus, or some IRC clients will open up another tab with the private message session.)

Twitter -> IRC Notifications

TwitterBot will send the tweets of friends of @nerdhaus to #nerdhaus, the same tweets as appear on the picture frame. (People nerdhaus is following on Twitter.)

If you have entered your twitter username/password, TwitterBot will also check your friends timeline and send you private messages with the tweets of your friends. This means when you are at your computer, you can turn off phone updates and you don't need to use Twitterific. You can use the IRC server to talk both ways to Twitter!

Control Twitter's delivery method for your account

Send !twitter on or !twitter off to turn on or off SMS notifications on Twitter. This is exactly like clicking the "phone" or "none" options on the Twitter website.

Alternatively, when you enter your Twitter username and password into the wiki, there is a checkbox there that can do this automatically for you. When you enter the channel, TwitterBot will tell Twitter to stop sending updates to your phone, and if you leave (or set your nick to nick|afk) Twitter will start sending to your phone again.

New Features

Lately I've been wanting certain Twitter hash tags to come to me in (near) real time, rather than constantly refreshing the Twitter search page. I've been using a script which reads the search RSS feed and sends those updates into a #tweets channel. But every time I want to follow a different topic, I have to change the code. I want this to be configurable via some simple IRC commands.

Adding a new search query:

  • Go to http://search.twitter.com and enter a query, including any modifiers such as AND, OR, or "-"
  • Join #tweetsearch
  • Copy the resulting URL and send it to the channel
  • You'll get a reply and an invite to a new channel you can join to see these search results. The channel name will be something like #tw-searchtag

Managing queries:

  • Send !list to #tweetsearch to get a list of the current channels
  • Send !delete #tw-searchtag to stop following that tag
  • Send !info #tw-searchtag to get a link to the twitter search page which created this channel


Source Code

(old version)

#!/usr/bin/perl

#***********************************
# 
# Twitter -> IRC
# 
# TwitterBot listens on a UDP port for incoming packets from the script that 
# checks nerdhaus' friends timeline. It passes any message it receives on this
# port to the chatroom.
# 
# 
# IRC -> Twitter
# 
# When TwitterBot sees a message beginning in Twitter:, it knows it is a command
# to send a message to that user's Twitter account. First, a whois is performed 
# on the user sending the message, to find that user's real username. This is because
# a user's nick may be (is probably) different from their registered username. 
# The bot is connected to the database and looks up a user's Twitter credentials
# given their username. It then composes a POST to Twitter's API on behalf of the user.
# 
# Note: No security is performed here to ensure a user is really who they say they
# are. We are trusting everyone to only sign in as themselves. If this proves to
# be a problem, we could implement some sort of authentication. Let's hope not.
# 
#************************************


use warnings;
use strict;

use POE;
use Config::File;
use IO::Socket::INET;
use POE::Component::IRC;
use Data::Dumper;
use DBI;
use HTTP::Request::Common qw(POST);
use LWP::UserAgent;

use constant DATAGRAM_MAXLEN => 1024;

select((select(STDOUT), $|=1)[0]);

my $conf = Config::File::read_config_file("twitterbot.conf");

my $ua = LWP::UserAgent->new;

# Create the component that will represent an IRC network.
my ($irc) = POE::Component::IRC->spawn();

my $dbh = get_dbh();
my $q_twitterauth = prepare_twitterauth($dbh);


my $last_message = ""; # this is slightly sketchy using a single variable to hold all messages,
 # but unless I can figure out a way to asynchronously link the message and the whois reply,
 # it will have to do for now.


# Create the bot session.  The new() call specifies the events the bot
# knows about and the functions that will handle those events.
POE::Session->create(
        inline_states => {
        _start     => \&bot_start,
        irc_001    => \&on_connect,
        irc_public => \&on_public,
        irc_whois  => \&on_whois,
    },
);

POE::Session->create(
    inline_states => {
        _start       => \&server_start,
        get_datagram => \&server_read,
      }
);

$poe_kernel->run();


exit;

sub get_dbh {
    my $dbs = $conf->{DSN} || "mysql";
    my $db_name = $conf->{DATABASE} || "twitterbot";
    my $host = $conf->{HOST} || "localhost";
    my $user = $conf->{USER} || "twitterbot";
    my $passwd = $conf->{PASSWORD} || "";

    my $ts = scalar localtime;
    print " [$ts] Establishing database connection...\n";
    my $db_dsn = "DBI:$dbs:database=$db_name;host=$host";
    my $dbh = DBI->connect($db_dsn, $user, $passwd,
            {RaiseError=>1, AutoCommit => 1});
    return $dbh;
}

sub prepare_twitterauth {
	my $mdbh = shift;
	return $mdbh->prepare('SELECT twitter_username, twitter_password FROM `irc-twitterbot` WHERE username=?');
}

# UDP Server
sub server_start {
    my $kernel = $_[KERNEL];

    my $socket = IO::Socket::INET->new(
        Proto     => 'udp',
        LocalPort => $conf->{UDP_LISTEN},
    );

    die "Couldn't create server socket: $!" unless $socket;
    $kernel->select_read( $socket, "get_datagram" );
}

sub server_read {
    my ( $kernel, $socket ) = @_[ KERNEL, ARG0 ];
    my $ircmessage = "";
    recv( $socket, my $message = "",  DATAGRAM_MAXLEN, 0 );
	print "$message\n";

	$ircmessage = $message;

    $irc->yield( privmsg => $conf->{IRC_CHANNEL}, $ircmessage );
}



# IRC Server

# The bot session has started.  Register this bot with the "magnet"
# IRC component.  Select a nickname.  Connect to a server.
sub bot_start {
    my $kernel  = $_[KERNEL];
    my $heap    = $_[HEAP];
    my $session = $_[SESSION];

    $irc->yield( register => "all" );

    $irc->yield( connect =>
          { Nick 	=> $conf->{IRC_NICK},
            Username 	=> $conf->{IRC_USERNAME},
				Password => $conf->{IRC_SERVER_PW},
            Ircname  	=> $conf->{IRC_NAME},
            Server   	=> $conf->{IRC_SERVER},
            Port     	=> $conf->{IRC_PORT},
          }
         );

}

# The bot has successfully connected to a server.  Join a channel.
sub on_connect {
	my $ts = scalar localtime;
	print " [$ts] Joining channel ".$conf->{IRC_CHANNEL}."\n";
	$irc->yield( join => $conf->{IRC_CHANNEL} );
}

# The bot has received a public message.  Parse it for commands, and
# respond to interesting things.
sub on_public {
	my ( $kernel, $who, $where, $msg ) = @_[ KERNEL, ARG0, ARG1, ARG2 ];
	my $nick = ( split /!/, $who )[0];
	my $channel = $where->[0];

	my $ts = scalar localtime;
	if ( substr($msg,0,9) eq "Twitter: " ) {
		$last_message = $msg;
		print " [$ts] <$nick:$channel> $msg\n";
		$irc->yield( whois => $nick );
	}
}

sub on_whois {
	my $whois = $_[10];
	my $username = $whois->{'user'};
	my $ts = scalar localtime;

	if ( !$dbh->ping ) {
		print " [$ts] database has gone away. reconnecting...\n";
		$dbh = get_dbh();
		$q_twitterauth = prepare_twitterauth($dbh);
	}

	my $twitter_username = '';
	my $twitter_password = '';

	my $tweet = substr($last_message,9);
	my @sql_args = (lc($username));
	$q_twitterauth->execute(@sql_args);
	while( my @row = $q_twitterauth->fetchrow_array ){
		$twitter_username = $row[0];
		$twitter_password = $row[1];
	}	

	if( $twitter_username eq '' ) {

		print " [$ts] $username: No Twitter auth found\n";
		$irc->yield( privmsg => $conf->{IRC_CHANNEL}, $whois->{'nick'}.": I don't have your Twitter account info, or your username is not set to your first name. Visit http://pin13.net/n/Special:TwitterIRC, then try again" );

	} else {

		print " [$ts] $username (".$twitter_username.'): '.$tweet."\n";

		my $req = POST 'https://twitter.com/statuses/update.xml',
			[ 	status => $tweet ];
		$req->authorization_basic( $twitter_username, $twitter_password );

		my $response = $ua->request($req)->as_string;
		if( substr($response,0,12) eq 'HTTP/1.1 401' ) {
			print " [$ts] $username (".$twitter_username."): Bad username/password\n";
			$irc->yield( privmsg => $conf->{IRC_CHANNEL}, $whois->{'nick'}.": Twitter rejected your username. Visit http://pin13.net/n/Special:TwitterIRC and update your password" );
		} else {
			print " [$ts] $username (".$twitter_username."): Success!\n";
			$irc->yield( privmsg => $conf->{IRC_CHANNEL}, $whois->{'nick'}.": I posted your message! See it here: http://twitter.com/".$twitter_username );
		}

	}

}

Config File

DSN = mysql
DATABASE = twitterbot
HOST = localhost
USER = irc
PASSWORD = 
IRC_SERVER = irc.example.com
IRC_PORT = 6667
IRC_SERVER_PW = 
IRC_CHANNEL = \#nerdhaus
IRC_USERNAME = TwitterBot
IRC_NICK = TwitterBot
IRC_NAME = Nerdhaus TwitterBot
UDP_LISTEN = 9999

Database

CREATE TABLE `irc-twitterbot` (
  `username` varchar(20) NOT NULL,
  `twitter_username` varchar(40) default NULL,
  `twitter_password` varchar(40) default NULL,
  PRIMARY KEY  (`username`)
)