The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package App::Maisha::Plugin::Twitter;

use strict;
use warnings;

our $VERSION = '0.18';

#----------------------------------------------------------------------------
# Library Modules

use base qw(App::Maisha::Plugin::Base);
use base qw(Class::Accessor::Fast);
use File::Path;
use Net::Twitter;
use Storable;

#----------------------------------------------------------------------------
# Accessors

__PACKAGE__->mk_accessors($_) for qw(api users config);

#----------------------------------------------------------------------------
# Public API

#Request token URL
#https://api.twitter.com/oauth/request_token

#Access token URL
#https://api.twitter.com/oauth/access_token

#Authorize URL
#https://api.twitter.com/oauth/authorize

# http://dev.twitter.com/apps/347040

sub new {
    my $class = shift;
    my $self = {
        consumer_key    => 'ifCuNOQXA5KTnKVXVcZg',
        consumer_secret => 'OXyHE1PSgfy66gbCu3QshXgP9RNA1fOVLdqv4afPDug',
    };

    bless $self, $class;
    return $self;
}

sub login {
    my ($self,$config) = @_;
    my $api;

    unless($config->{username}) { warn "No username supplied\n"; return }

    eval {
        $api = Net::Twitter->new(
            traits              => [qw/API::Search API::REST OAuth/],
            consumer_key        => $self->{consumer_key},
            consumer_secret     => $self->{consumer_secret},
            ssl                 => 1
        );
    };

    unless($api) {
        warn "Unable to establish connection to Twitter API\n";
        return 0;
    }

    # for testing purposes we don't want to login
    if(!$config->{test}) {
        eval {
            my $datafile = $config->{home} . '/.maisha/twitter.dat';
            my $access_tokens = eval { retrieve($datafile) } || {};

            if ( $access_tokens && $access_tokens->{$config->{username}}) {
                $api->access_token($access_tokens->{$config->{username}}->[0]);
                $api->access_token_secret($access_tokens->{$config->{username}}->[1]);
            } else {
                my $auth_url = $api->get_authorization_url;
                print " Authorize this application at: $auth_url\nThen, enter the PIN# provided to continue: ";

                my $pin = <STDIN>; # wait for input
                chomp $pin;

                unless($pin) {
                    warn "No PIN provided, Maisha will not be able to access Twitter account until authorized to do so\n";
                    return 0;
                }

                # request_access_token stores the tokens in $nt AND returns them
                my $access_tokens = {};
                my @access_tokens;
                eval { @access_tokens = $api->request_access_token(verifier => $pin) };
                unless(@access_tokens) {
                    warn "Invalid PIN provided, Maisha will not be able to access your Twitter account until authorized to do so\n";
                    return 0;
                }
                $access_tokens->{$config->{username}} = \@access_tokens;

                mkpath( $config->{home} . '/.maisha' );

                # save the access tokens
                store $access_tokens, $datafile;
                chmod 0640, $datafile;  # make sure it has reasonable permissions
            }
        };

        if($@) {
            warn "Unable to login to Twitter\n";
            return 0;
        }
    }

    $self->api($api);
    $self->config($config);

    if(!$config->{test}) {
        print "...building user cache for Twitter\n";
        $self->_build_users();
    }

    return 1;
}

sub _build_users {
    my $self = shift;
    my %users;

    eval {
        my $f = $self->api->friends();
        if($f && @$f)   { for(@$f) { next unless($_); $users{$_->{screen_name}} = 1 } }
        $f = $self->api->followers();
        if($f && @$f)   { for(@$f) { next unless($_); $users{$_->{screen_name}} = 1 } }
    };

    $self->users(\%users);
}

sub api_reauthorize {
    my $self    = shift;
    my $config  = $self->config;
    my $api     = $self->api;

    # for testing purposes we don't want to login
    if(!$config->{test}) {
        eval {
            my $datafile = $config->{home} . '/.maisha/twitter.dat';

            my $auth_url = $api->get_authorization_url;
            print "Please re-authorize this application at: $auth_url\nThen, enter the PIN# provided to continue: ";

            my $pin = <STDIN>; # wait for input
            chomp $pin;

            unless($pin) {
                warn "No PIN provided, Maisha will not be able to access direct messages until reauthorization is completed\n";
                return 0;
            }

            # request_access_token stores the tokens in $nt AND returns them
            my $access_tokens = {};
            my @access_tokens;
            eval { @access_tokens = $api->request_access_token(verifier => $pin) };
            unless(@access_tokens) {
                warn "Invalid PIN provided, Maisha will not be able to access direct messages until reauthorization is completed\n";
                return 0;
            }
            $access_tokens->{$config->{username}} = \@access_tokens;

            unlink $datafile;
            mkpath( $config->{home} . '/.maisha' );

            # save the access tokens
            store $access_tokens, $datafile;
            chmod 0640, $datafile;  # make sure it has reasonable permissions
        };

        if($@) {
            warn "Unable to login to Twitter\n";
            return 0;
        }
    }

    return 1;
}

sub api_follow {
    my $self = shift;
    $self->api->create_friend(@_);
}

sub api_unfollow {
    my $self = shift;
    $self->api->destroy_friend(@_);
}

sub api_user {
    my $self = shift;
    $self->api->show_user(@_);
}

sub api_user_timeline {
    my $self = shift;
    $self->api->user_timeline(@_);
}

sub api_friends {
    my $self = shift;
    $self->api->following(@_);
}

sub api_friends_timeline {
    my $self = shift;
    $self->api->following_timeline(@_);
}

sub api_public_timeline {
    my $self = shift;
    $self->api->public_timeline(@_);
}

sub api_followers {
    my $self = shift;
    $self->api->followers(@_);

    # below is meant to be the same as the above, but it isn't :(
    #$self->api->lookup_users( { user_id => $self->api->followers_ids() } );
}

sub api_update {
    my $self = shift;
    $self->api->update(@_);
}

sub api_replies {
    my $self = shift;
    $self->api->replies(@_);
}

sub api_send_message {
    my $self = shift;
    $self->api->new_direct_message(@_);
}

sub api_direct_messages_to {
    my $self = shift;
    $self->api->direct_messages(@_);
}

sub api_direct_messages_from {
    my $self = shift;
    $self->api->sent_direct_messages(@_);
}

sub api_search {
    my $self = shift;
    $self->api->search(@_);
}

1;

__END__

=head1 NAME

App::Maisha::Plugin::Twitter - Maisha interface to Twitter

=head1 SYNOPSIS

   maisha
   maisha> use Twitter
   use ok

=head1 DESCRIPTION

App::Maisha::Plugin::Twitter is the gateway for Maisha to access the Twitter
API.

=head1 METHODS

=head2 Constructor

=over 4

=item * new

=back

=head2 Process Methods

=over 4

=item * login

Login to the service. See Authentication below.

=back

=head2 API Methods

The API methods are used to interface to with the Twitter API.

=over 4

=item * api_reauthorize

=item * api_follow

=item * api_unfollow

=item * api_user

=item * api_user_timeline

=item * api_friends

=item * api_friends_timeline

=item * api_public_timeline

=item * api_followers

=item * api_update

=item * api_replies

=item * api_send_message

=item * api_direct_messages_to

=item * api_direct_messages_from

=item * api_search

=back

=head1 AUTHENTICATION

On 31st August 2010, Twitter disabled Basic Authentication to access their API.
Instead they have introduce the OAuth method of authrntication, which now 
requires application developers to request the user to authenticate themselves 
and provide a PIN (Personal Identification Number) to allow the application to
retrieve access tokens.

With this new method of authentication, the application will provide a URL,
which the user needs to cut-n-paste into a browser to logging in to the 
service, using your regular username/password, then 'Allow' Maisha to access 
your account. This will then allow Maisha to post to your account. You will 
then be given a PIN, which should then be entered at the prompt on the Maisha
command line.

Once you have completed authentication, the application will then store your 
access tokens permanently under your profile on your computer. Then when you
next use the application it will retrieve these access tokens automatically and
you will no longer need to register the application.

=head1 SEE ALSO

For further information regarding the commands and configuration, please see
the 'maisha' script included with this distribution.

L<App::Maisha>

L<Net::Twitter>

=head1 WEBSITES

=over 4

=item * Main Site: L<http://maisha.grango.org>

=item * Git Repo:  L<http://github.com/barbie/maisha/tree/master>

=item * RT Queue:  L<RT: http://rt.cpan.org/Public/Dist/Display.html?Name=App-Maisha>

=back

=head1 AUTHOR

  Barbie, <barbie@cpan.org>
  for Miss Barbell Productions <http://www.missbarbell.co.uk>.

=head1 COPYRIGHT AND LICENSE

  Copyright (C) 2009-2012 by Barbie

  This module is free software; you can redistribute it and/or
  modify it under the Artistic License v2.

=cut