From 92e646ff78152142bc63c4789c4051121c142950 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 13 Sep 2011 23:20:38 -0500 Subject: initial implementation --- .gitignore | 1 + bin/update_twitter | 46 ++++++++++++ dist.ini | 10 +++ lib/Linkulator/Twitter.pm | 163 +++++++++++++++++++++++++++++++++++++++++ lib/Linkulator/Twitter/Link.pm | 25 +++++++ 5 files changed, 245 insertions(+) create mode 100644 bin/update_twitter create mode 100644 lib/Linkulator/Twitter/Link.pm diff --git a/.gitignore b/.gitignore index e004fc9..b44c88f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ MANIFEST.bak Linkulator-Twitter-* *.bs *.o +state.json diff --git a/bin/update_twitter b/bin/update_twitter new file mode 100644 index 0000000..c5eb888 --- /dev/null +++ b/bin/update_twitter @@ -0,0 +1,46 @@ +#!/usr/bin/env perl +use strict; +use warnings; +# PODNAME: update_twitter + +use JSON; +use Linkulator::Twitter; +use Path::Class; +use Try::Tiny; + +my $state_file = 'state.json'; +if (!-e $state_file) { + file($state_file)->openw->print(encode_json({last_tweeted => 0})); +} + +my $conf = decode_json(file($state_file)->slurp); + +my $lt = Linkulator::Twitter->new( + exists $conf->{token} && exists $conf->{token_secret} + ? (twitter_access_token => $conf->{token}, + twitter_access_token_secret => $conf->{token_secret}) + : (), +); + +if (my $url = $lt->authenticate_twitter) { + print "Authorize this app at $url and enter the pin here: "; + + my $pin = ; + chomp $pin; + + my ($token, $secret) = $lt->authenticate_twitter($pin); + $conf->{token} = $token; + $conf->{token_secret} = $secret; +} + +try { + $lt->update; + for my $link (sort { $a->id <=> $b->id } $lt->links) { + next if $link->id <= $conf->{last_tweeted}; + $lt->tweet($link); + $conf->{last_tweeted} = $link->id; + } +} +finally { + file($state_file)->openw->print(encode_json($conf)); +}; diff --git a/dist.ini b/dist.ini index 7b6fa35..fee4b26 100644 --- a/dist.ini +++ b/dist.ini @@ -7,3 +7,13 @@ copyright_holder = Jesse Luehrs dist = Linkulator-Twitter [Prereqs] +JSON = 0 +Moose = 2.0200 +namespace::autoclean = 0 +Net::Twitter = 3.16000 +Path::Class = 0 +String::Truncate = 0 +Try::Tiny = 0 +WWW::Shorten = 0 +WWW::Shorten::VGd = 0 +XML::RAI = 0 diff --git a/lib/Linkulator/Twitter.pm b/lib/Linkulator/Twitter.pm index e69de29..84134ce 100644 --- a/lib/Linkulator/Twitter.pm +++ b/lib/Linkulator/Twitter.pm @@ -0,0 +1,163 @@ +package Linkulator::Twitter; +use Moose; +use namespace::autoclean; +# ABSTRACT: scrape linkulator urls and turn them into tweets + +use LWP::UserAgent; +use Net::Twitter; +use String::Truncate 'elide'; +use WWW::Shorten 'VGd'; +use XML::RAI; + +use Linkulator::Twitter::Link; + +has feed_url => ( + is => 'ro', + isa => 'Str', + default => 'http://offtopic.akrasiac.org/?feed=sfw', +); + +has twitter_user => ( + is => 'ro', + isa => 'Str', + default => 'crawl_offtopic', +); + +has twitter_access_token => ( + is => 'ro', + isa => 'Str', + predicate => 'has_twitter_access_token', +); + +has twitter_access_token_secret => ( + is => 'ro', + isa => 'Str', + predicate => 'has_twitter_access_token_secret', +); + +has links => ( + traits => ['Array'], + isa => 'ArrayRef[Linkulator::Twitter::Link]', + default => sub { [] }, + handles => { + links => 'elements', + add_link => 'push', + }, +); + +has twitter_consumer_key => ( + is => 'ro', + isa => 'Str', + default => '6mW3vek1Edty1NGJe7yPFg', +); + +has twitter_consumer_secret => ( + is => 'ro', + isa => 'Str', + default => 'OzQuhNQ4HlO2eQw9tdo8R4QDLYqORwSEIzkF6ZBCAY', +); + +has ua => ( + is => 'ro', + isa => 'LWP::UserAgent', + default => sub { LWP::UserAgent->new }, +); + +has twitter => ( + is => 'ro', + isa => 'Net::Twitter', + lazy => 1, + default => sub { + my $self = shift; + Net::Twitter->new( + traits => ['API::REST', 'OAuth'], + consumer_key => $self->twitter_consumer_key, + consumer_secret => $self->twitter_consumer_secret, + ($self->has_twitter_access_token + ? (access_token => $self->twitter_access_token) + : ()), + ($self->has_twitter_access_token_secret + ? (access_token_secret => $self->twitter_access_token_secret) + : ()), + ); + }, +); + +sub authenticate_twitter { + my $self = shift; + my ($pin) = @_; + + return if $self->twitter->authorized; + + return $self->twitter->get_authorization_url + if !defined $pin; + + my ($token, $secret, $id, $user) = $self->twitter->request_access_token( + verifier => $pin, + ); + die "Authenticated the wrong user: $user (should be " . $self->twitter_user . ')' + unless $self->twitter_user eq $user; + + return ($token, $secret); +} + +sub update { + my $self = shift; + + my $res = $self->ua->get($self->feed_url); + die "couldn't get " . $self->feed_url . " : " . $res->status_line + unless $res->is_success; + + my $rss = $self->_munge_xml($res->content); + + my $feed = XML::RAI->parse_string($rss); + die "got no items!" unless $feed->item_count; + + for my $item (@{ $feed->items }) { + my ($link_num) = ($item->identifier =~ /(\d+)$/); + my $uri = $item->link; + $uri =~ s/^\s+|\s+$//g; + my $desc = $item->title; + $desc =~ s/^\s+|\s+$//g; + $self->add_link( + Linkulator::Twitter::Link->new( + id => $link_num, + uri => $uri, + desc => $desc, + ) + ); + } +} + +sub tweet { + my $self = shift; + my ($link) = @_; + + die "not a link" + unless blessed($link) && $link->isa('Linkulator::Twitter::Link'); + + my $uri = makeashorterlink($link->uri); + my $desc = elide($link->desc, 140 - length($uri) - 1, { at_space => 1 }); + + $self->twitter->update("$desc $uri"); +} + +# the feed produces invalid xml - the and tags don't escape +# ampersands in urls, and the xml parser chokes on this. need to fix it up +# here. +sub _munge_xml { + my $self = shift; + my ($xml) = @_; + + $xml =~ s#<(link|guid)(.*)>([^<]*)# + my ($tag, $attrs, $text) = ($1, $2, $3); + $text =~ s+&+&+g; + "<${tag}${attrs}>$text" + #eg; + + return $xml; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/Linkulator/Twitter/Link.pm b/lib/Linkulator/Twitter/Link.pm new file mode 100644 index 0000000..9d8ae68 --- /dev/null +++ b/lib/Linkulator/Twitter/Link.pm @@ -0,0 +1,25 @@ +package Linkulator::Twitter::Link; +use Moose; + +has id => ( + is => 'ro', + isa => 'Int', + required => 1, +); + +has uri => ( + is => 'ro', + isa => 'Str', + required => 1, +); + +has desc => ( + is => 'ro', + isa => 'Str', + required => 1, +); + +__PACKAGE__->meta->make_immutable; +no Moose; + +1; -- cgit v1.2.3-54-g00ecf