#!/usr/bin/env perl # vl-twitget - simple command-line Twitter client to get one's latest # tweets and retweets as plain text. # Copyright 2010-2017 Vincent Lefevre . # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA # To register an application and get a consumer key/secret pair: # https://apps.twitter.com/ # The consumer key and the consumer secret must be stored in # a ~/.twitget file on two lines. # See https://developer.twitter.com/en/docs for the API, in particular: # https://developer.twitter.com/en/docs/tweets/tweet-updates use strict; use open OUT => ':locale'; use TwitGet; use Date::Parse; use POSIX; my ($proc) = '$Id: twitget 103810 2017-11-25 03:14:25Z vinc17/zira $' =~ /^.Id: (\S+) \d+ / or die; my $rev = $ARGV[0] eq '-r' and shift; my $ss = $rev ? sub { $b cmp $a } : sub { $a cmp $b }; my %param; foreach (@ARGV) { /^(\w+)=(.*)/ or die "Usage: $proc [-r] [ key=value ... ]\n"; $param{$1} = $2; } STDERR->autoflush(1); my $eol = -t STDERR ? eval { require Term::Cap; my $terminal = Tgetent Term::Cap; $terminal->Tputs('ce'); } || "\n" : ""; my $nt = TwitGet::init(); $param{include_rts} = 1; $param{tweet_mode} = 'extended'; my ($ntweets,$nrts) = (0,0); my @timeline; sub src ($) { my $s = $_[0]->{source}; $s =~ s:^(.*?)$:$1:; return $s; } sub pl ($$) { "@_".($_[0] > 1 ? 's' : '') } my $tget = sub (\%) { my $param = $_[0]; my ($minid,$maxid); print STDERR "$proc: request...\r" if $eol; my @t = sort $ss map { $ntweets++; my $status = $_; my $id = $status->{id}; defined $minid && $id >= $minid or $minid = $id; defined $maxid && $id <= $maxid or $maxid = $id; my $created = str2time($status->{created_at}); defined $created or die "$proc: can't parse date for id $id\n"; my $text = $status->{full_text}; my $rf = ''; my $rt = $status->{retweeted_status}; if (defined $rt) { $nrts++; # Note: $status->{truncated} now seems to be always false. # So, no longer check it. $text = "RT @".$rt->{user}{screen_name}.": $rt->{full_text}"; $rt = " [RT of $rt->{id} from ".src($rt)."]"; } else { my $rc = $status->{retweet_count}; my $fc = $status->{favorite_count}; if ($rc + $fc > 1) { my @rf; $rc and push @rf, pl($rc,"retweet"); $fc and push @rf, pl($fc,"favorite"); $rf = "{".join(" / ",@rf)."}\n"; } } $text =~ s/\n/ /g; $text =~ s/<//g; $text =~ s/&/&/g; foreach my $u (@{$status->{entities}{urls}}) { $text =~ s/\Q$u->{url}\E/$u->{expanded_url}/g } foreach my $u (@{$status->{entities}{media}}) { $text =~ s/\Q$u->{url}\E/$u->{media_url}/g } my $coord = $status->{coordinates}; my $cstr = ''; if (defined $coord) { my $type = $coord->{type}; if ($type eq 'Point') { $cstr = "Coordinates: (".join(',',@{$coord->{coordinates}}).")\n"; } else { warn "Bad coordinates type for $id: $type\n"; } } POSIX::strftime("[%FT%TZ] ", gmtime $created). "https://twitter.com/$status->{user}{screen_name}/status/$id\n". "$text\nSource: ".src($status)."$rt\n$cstr$rf\n"; } (@{$nt->user_timeline($param)}); if (@t) { print STDERR "$proc: got tweets from $minid to $maxid\n"; if ($rev) { push @timeline, @t } else { unshift @timeline, @t } return $minid; } else { print STDERR $eol; return; } }; TwitGet::loop($nt,\%param,$tget); print @timeline, "$ntweets tweets, including $nrts retweets.\n";