Created
January 3, 2023 11:41
-
-
Save Gro-Tsen/4dd55cc6825d1ea19e06ca52e580858d to your computer and use it in GitHub Desktop.
Twitter Perl scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/local/bin/perl -w | |
# gettweet.pl: Retrieve the content of tweets by id. | |
# Usage: getweet.pl -i <comma-separated-list> | |
# saves each in a file called <fulldate>-<id>.txt | |
# Requires API keys to be stored in ~/.twitterkeys (or $TWITTERKEYS) | |
# syntax being four lines starting "API-Key: ", "API-Key-Secret: ", | |
# "Access-Token: " and "Access-Token-Secret: " each followed by their value. | |
use strict; | |
use warnings; | |
use Getopt::Std; | |
use Net::Twitter; | |
use DateTime::Format::Strptime; | |
use Data::Dumper; | |
binmode STDERR, ":utf8"; | |
binmode STDOUT, ":utf8"; | |
# Get options | |
my %opts; | |
getopts("i:", \%opts); | |
# -i <ids>: retrieve specific tweets (comma separated list) | |
my $lookuplist; | |
if ( defined($opts{i}) ) { | |
die "Badly formed argument to -i (expecting a comma-separated list of integers)" unless $opts{i} =~ m/^[0-9]+(?:\,[0-9]+)*$/; | |
$lookuplist = [ split(/,/, $opts{i}) ]; | |
} else { | |
die "Need to pass -i option"; | |
} | |
# Read authentication file | |
my $keyfname = $ENV{"TWITTERKEYS"} // ($ENV{"HOME"}."/.twitterkeys"); | |
my %keyfdata; | |
open my $keyf, "<", $keyfname or die "Can't open $keyfname: $!"; | |
while (<$keyf>) { | |
s/\#.*//; | |
next if m/^\s+$/; | |
die "Bad key file format: $_" unless m/^\s*([A-Za-z][A-Za-z0-9\-]*)\:\s+(\S+)\s*$/; | |
# print STDERR "Read auth data \[$1\]=$2\n"; | |
$keyfdata{$1} = $2; | |
} | |
close $keyf; | |
my $consumer_key = $keyfdata{"API-Key"} or die "Didn't find API-Key in key file"; | |
my $consumer_secret = $keyfdata{"API-Key-Secret"} or die "Didn't find API-Key-Secret in key file"; | |
my $access_token = $keyfdata{"Access-Token"} or die "Didn't find Access-Token in key file"; | |
my $access_token_secret = $keyfdata{"Access-Token-Secret"} or die "Didn't find Access-Token-Secret in key file"; | |
die "Badly formed Access-Token" unless $access_token =~ m/^([0-9]+)\-/; | |
my $self_id = $1; | |
# Open the Twitter connection. | |
my $nt = Net::Twitter->new( | |
traits => [qw/API::RESTv1_1/], | |
consumer_key => $consumer_key, | |
consumer_secret => $consumer_secret, | |
access_token => $access_token, | |
access_token_secret => $access_token_secret, | |
); | |
die "Couldn't open Twitter connection" unless $nt; | |
my $datetime_parser = DateTime::Format::Strptime->new( | |
pattern => "%a %b %d %T %z %Y", time_zone => "UTC", locale => "C", | |
on_error => "croak"); | |
# Get self screen name | |
print STDERR "Getting information on self...\n"; | |
my $slf = $nt->lookup_users({user_id => $self_id}); | |
die "lookup_user didn't return exactly one user" unless scalar(@{$slf})==1; | |
die "Missing screen name" unless defined($slf->[0]->{"screen_name"}); | |
my $self_screen_name = $slf->[0]->{"screen_name"}; | |
my $self_name = $slf->[0]->{"name"}; | |
my %paramdict; | |
my $tweetlist; | |
if ( 1 ) { | |
%paramdict = (trim_user => 0, tweet_mode => "extended"); | |
$paramdict{id} = $lookuplist; | |
$tweetlist = $nt->lookup_statuses(\%paramdict); | |
} | |
# The following function performs a set of substitutions on a string | |
# (correctly adjusting the position of each subsequent substitution to | |
# take into account earlier ones). | |
sub substitute_in_string { | |
my $str = shift; | |
my @subs = sort { $a->[0] <=> $b->[0] } @{ shift() }; | |
# Each entry of @subs is an array reference: | |
# [0]: first character of substring to modify | |
# [1]: length of substring to modify | |
# [2]: undefined, or substring itself, to check for consistency | |
# [3]: undefined, or substring to substitute | |
# [4]: undefined, or substring to insert before | |
# [5]: undefined, or substring to insert at end | |
my $corr = 0; | |
my $minbar = 0; | |
for my $sb ( @subs ) { | |
my $idx0 = $sb->[0] + $corr; | |
die "Attempting to overlap substitutions" unless $idx0>=$minbar; | |
my $len = $sb->[1]; | |
if ( defined($sb->[2]) ) { | |
die(sprintf("Bad calibration: expecting \"%s\", got \"%s\"\n", $sb->[2], substr($str, $idx0, $len))) unless substr($str, $idx0, $len) eq $sb->[2]; | |
} | |
my $repl = ($sb->[4] // "") . ($sb->[3] // substr($str, $idx0, $len)) . ($sb->[5] // ""); | |
my $newlen = length($repl); | |
# printf STDERR "str: %s\nsubstituting (%d,%d) \"%s\"->\"%s\"\n", $str, $idx0, $len, substr($str, $idx0, $len), $repl; | |
substr($str, $idx0, $len) = $repl; | |
# printf STDERR "str: %s\n", $str; | |
$corr += ($newlen - $len); | |
$minbar = $idx0 + $newlen; | |
} | |
return $str; | |
} | |
# Now scan through tweetlist | |
for my $st ( @{$tweetlist} ) { | |
die "Missing id from tweetlist entry" unless defined($st->{"id"}); | |
die "Bad id from tweetlist entry" unless $st->{"id"} =~ /^[0-9]+$/; | |
my $created_at = $datetime_parser->parse_datetime($st->{"created_at"}); | |
my $timestamp = $created_at->strftime("%Y%m%d%H%M%S"); | |
my $id = $st->{"id"}; | |
printf STDERR "%s-%s\n", $timestamp, $id; | |
my $fname = $timestamp . "-" . $id . ".txt"; | |
my $user_id = $st->{"user"}->{"id"}; | |
my $user_screen_name = $user_id eq $self_id ? $self_screen_name : $st->{"user"}->{"screen_name"}; | |
die "Didn't get user screen name" unless defined($user_screen_name); | |
my $permalink = sprintf("https://twitter.com/%s/status/%s", $user_screen_name, $id); | |
my $fulltext = $st->{"full_text"}; | |
die "Text contains unescaped HTML" if $fulltext =~ m/[\<\>]|\&(?!(?:lt|gt|amp|apos|quot|\#(?:x[0-9A-Fa-f]+|[0-9]+))\;)/; | |
my @substitutions = (); | |
for my $ent ( @{$st->{"entities"}->{"urls"}} ) { | |
my $idx0 = $ent->{"indices"}->[0]; | |
my $idx1 = $ent->{"indices"}->[1]; | |
push @substitutions, [$idx0, $idx1-$idx0, $ent->{"url"}, $ent->{"expanded_url"}]; | |
} | |
for my $ent ( @{$st->{"entities"}->{"media"}} ) { | |
my $idx0 = $ent->{"indices"}->[0]; | |
my $idx1 = $ent->{"indices"}->[1]; | |
push @substitutions, [$idx0, $idx1-$idx0, $ent->{"url"}, $ent->{"display_url"}]; | |
} | |
$fulltext = substitute_in_string $fulltext, \@substitutions; | |
# Save text archive in a file | |
open my $f, ">:utf8", $fname or die "Cannot open $fname"; | |
print $f "$timestamp\n$id ($user_id)\n$permalink\n\n$fulltext\n"; | |
close $f; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/local/bin/perl -w | |
# tweet.pl: Post a tweet on Twitter. | |
# Reads tweet content from stdin unless -c option is specified (giving content). | |
# Use -r id to reply to specified tweet ID. Use of -m is preferable (required?). | |
# Use -n to chomp final NL (useful when reading from stdin). | |
# The id of the posted tweet is written on stdout, various diagnostics on stderr. | |
# Requires API keys to be stored in ~/.twitterkeys (or $TWITTERKEYS) | |
# syntax being four lines starting "API-Key: ", "API-Key-Secret: ", | |
# "Access-Token: " and "Access-Token-Secret: " each followed by their value. | |
use strict; | |
use warnings; | |
use Encode; | |
use Getopt::Std; | |
use Net::Twitter; | |
# use DateTime::Format::Strptime; | |
# use Data::Dumper; | |
use open IN => ':utf8'; | |
binmode STDIN, ":utf8"; | |
binmode STDERR, ":utf8"; | |
binmode STDOUT, ":utf8"; | |
# Get options | |
my %opts; | |
getopts("r:c:mn", \%opts); | |
# -r <id>: reply to specified ID | |
# -c <string>: status content | |
# -m: set auto_populate_reply_metadata | |
# -n: chomp final NL | |
my $in_reply_to; | |
if ( defined($opts{r}) ) { | |
die "Badly formed argument to -r" unless $opts{r} =~ m/^(?:https?\:\/\/twitter\.com\/[^\/]*\/status\/)?([0-9]+)$/; | |
$in_reply_to = $1; | |
} | |
my $auto_populate_reply_metadata; | |
$auto_populate_reply_metadata = 1 if $opts{m}; | |
# Read authentication file | |
my $keyfname = $ENV{"TWITTERKEYS"} // ($ENV{"HOME"}."/.twitterkeys"); | |
my %keyfdata; | |
open my $keyf, "<", $keyfname or die "Can't open $keyfname: $!"; | |
while (<$keyf>) { | |
s/\#.*//; | |
next if m/^\s+$/; | |
die "Bad key file format: $_" unless m/^\s*([A-Za-z][A-Za-z0-9\-]*)\:\s+(\S+)\s*$/; | |
# print STDERR "Read auth data \[$1\]=$2\n"; | |
$keyfdata{$1} = $2; | |
} | |
close $keyf; | |
my $consumer_key = $keyfdata{"API-Key"} or die "Didn't find API-Key in key file"; | |
my $consumer_secret = $keyfdata{"API-Key-Secret"} or die "Didn't find API-Key-Secret in key file"; | |
my $access_token = $keyfdata{"Access-Token"} or die "Didn't find Access-Token in key file"; | |
my $access_token_secret = $keyfdata{"Access-Token-Secret"} or die "Didn't find Access-Token-Secret in key file"; | |
die "Badly formed Access-Token" unless $access_token =~ m/^([0-9]+)\-/; | |
my $self_id = $1; | |
# Open the Twitter connection. | |
my $nt = Net::Twitter->new( | |
traits => [qw/API::RESTv1_1/], | |
consumer_key => $consumer_key, | |
consumer_secret => $consumer_secret, | |
access_token => $access_token, | |
access_token_secret => $access_token_secret, | |
); | |
die "Couldn't open Twitter connection" unless $nt; | |
# my $datetime_parser = DateTime::Format::Strptime->new( | |
# pattern => "%a %b %d %T %z %Y", time_zone => "UTC", locale => "C", | |
# on_error => "croak"); | |
# Read status to be tweeted | |
my $status; | |
if ( defined($opts{c}) ) { | |
$status = decode("utf-8", $opts{c}); | |
} else { | |
print STDERR "Reading tweet content...\n"; | |
{ local $/ = undef; $status = <>; } | |
} | |
chomp $status if defined($opts{n}); | |
print STDERR "Replying to $in_reply_to\n" if $in_reply_to; | |
print STDERR "Tweet to be posted follows:\n$status\n"; | |
my %hash; | |
$hash{status} = $status; | |
$hash{in_reply_to_status_id} = $in_reply_to if defined($in_reply_to); | |
$hash{auto_populate_reply_metadata} = 1 if $auto_populate_reply_metadata; | |
# Not sure if this is useful/desirable? | |
# <URL: https://developer.twitter.com/en/docs/tweets/tweet-updates.html > | |
$hash{tweet_mode} = "extended"; | |
my $tweet = $nt->update(\%hash); | |
print STDERR "Posted\n"; | |
print $tweet->{id} . "\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment