use strict;
use warnings;
use 5.020;
use experimental 'postderef';
use Net::Twitter;
use Path::Tiny;
use JSON;
use Try::Tiny;
use Safe::Isa;
use open ':std',':locale';
use Data::Printer;
sub get_twitter {
my $config_file = $0 =~ s{\.[^.]+$}{.json}r;
my $conf = decode_json(path($config_file)->slurp_raw);
return Net::Twitter->new(traits=>[
'API::RESTv1_1',
'AutoCursor',
'AutoCursor' => {
max_calls => 15,
force_cursor => 1,
array_accessor => 'users',
methods => [qw(friends followers)],
},
],%$conf);
}
sub fetch_lists_info {
my ($tw) = @_;
my $lists = $tw->list_ownerships({
count => 200,
});
my %lists_info = map {
$_->{id} => {
name => $_->{name},
},
} $lists->{lists}->@*;
for my $list_id (sort keys %lists_info) {
my $members = $tw->list_members({
list_id => $list_id,
count => 2000,
skip_status => 1,
include_entities => 0,
});
$lists_info{$list_id}->{members}={
map { $_->{id} => 1 } $members->{users}->@*,
};
}
return \%lists_info;
}
sub fetch_friends_info {
my ($tw) = @_;
my $friends = $tw->friends({
count => 200,
skip_status => 1,
include_user_entities => 0,
});
my %friends_info = map {
$_->{id} => { name => $_->{name} }
} $friends->@*;
return \%friends_info;
}
sub cache_file { $0 =~ s{\.[^.]+$}{-cache.json}r };
sub load_info {
return try { decode_json(path(cache_file)->slurp_raw) };
}
sub save_info {
path(cache_file)->spew_raw(encode_json({
lists=>$_[0],
friends=>$_[1],
}));
return;
}
sub to_list {
sort {
$a->{name} cmp $b->{name}
} map {
{ id => $_, $_[0]->{$_}->%* }
} keys $_[0]->%*
}
sub set_display_name {
my $max=0;
for my $e ($_[0]->@*) {
$e->{dn} = sprintf '%s (%d)',$e->@{qw(name id)};
my $l = length($e->{dn});
$max = $l if $max<$l;
}
return $max;
}
sub print_friends_lists_matrix {
my ($li,$fi) = @_;
my @lists = to_list($li);
my $list_width = set_display_name(\@lists);
my @friends = to_list($fi);
my $friend_width = set_display_name(\@friends);
print ' ' x ($friend_width+1);
for my $l (@lists) {
print $l->{dn},' ';
}
print "\n";
for my $f (@friends) {
printf '%-*s ',$friend_width,$f->{dn};
for my $l (@lists) {
printf '%-*s ',
length($l->{dn}),
( $l->{members}->{$f->{id}}
? '*' : '_' );
}
print "\n";
}
}
sub parse_friends_lists_matrix {
my ($fn) = @_;
my @lines = path($fn)->lines_utf8;
my @list_ids = $lines[0] =~ m{\((\d+)\)}g;
shift @lines;
my %lists;
for my $l (@lines) {
$l =~ s{\A.*?\((\d+)\)\s+}{} or next;
my $friend_id = $1;
}
}
my $tw = get_twitter;
try {
my $lists_info;
my $friends_info;
my $info = load_info();
if ($info) {
($lists_info,$friends_info) = $info->@{qw(lists friends)},
}
else {
$lists_info = fetch_lists_info($tw);
$friends_info = fetch_friends_info($tw);
save_info($lists_info,$friends_info);
}
print_friends_lists_matrix($lists_info,$friends_info);
} catch {
if ($_->$_isa('Net::Twitter::Error')) {
my $limit = $tw->rate_limit_status;
p $limit;
}
else { local $@=$_;die }
};