Skip to content

Instantly share code, notes, and snippets.

@sekia
Created July 10, 2015 06:03
Show Gist options
  • Select an option

  • Save sekia/6b8f504b166950ab6bba to your computer and use it in GitHub Desktop.

Select an option

Save sekia/6b8f504b166950ab6bba to your computer and use it in GitHub Desktop.
An old twitter bot.
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use opts;
use AnyEvent;
use AnyEvent::Twitter;
use Coro;
use Coro::Channel;
use Coro::Timer;
use Data::Dumper;
use Log::Dispatch;
opts
my $debug => +{ isa => 'Bool', default => 0, required => 1 },
my $log_file => +{ isa => 'Str' };
binmode STDOUT, ':encoding(utf8)';
binmode STDERR, ':encoding(utf8)';
my $twitter = AnyEvent::Twitter->new(
access_token => '',
access_token_secret => '',
consumer_key => '',
consumer_secret => '',
);
my $logger = Log::Dispatch->new(
outputs => (
$log_file
? [
[
FileShared => (
callbacks => \&modify_message,
filename => $log_file,
min_level => +($debug ? 'debug' : 'info'),
name => 'FileLogger',
newline => 1,
)
],
]
: [
[
Screen => (
callbacks => \&modify_message,
min_level => +($debug ? 'debug' : 'info'),
newline => 1,
)
],
]
),
);
my $unko_tweets = Coro::Channel->new;
async {
my $since_id = 0;
while ($since_id == 0) {
my ($header, $res, $reason) = search_unko_tweets($twitter, 1, $since_id);
unless ($header->{Status} eq '200' and $res->{max_id}) {
$logger->warn(sprintf('[search] Failed to fetch results: %s.', $reason));
sleep_thread(30);
next;
}
$since_id = $res->{max_id};
}
while (1) {
$logger->debug(sprintf('[search] since_id = %d.', $since_id));
my ($header, $res, $reason) = search_unko_tweets($twitter, 100, $since_id);
unless ($header->{Status} eq '200' and $res->{max_id}) {
$logger->warn(sprintf('[search] Failed to fetch results: %s.', $reason));
next;
}
$since_id = $res->{max_id};
for my $tweet (grep { is_unko_tweet($_) } @{ $res->{results} }) {
if ($tweet->{text}) {
$unko_tweets->put($tweet);
cede;
} else {
$logger->info(
sprintf('[search] Empty hash returned from API: %s.', Dumper($tweet))
);
}
}
} continue {
sleep_thread(30);
}
};
async {
while (1) {
my $tweet = $unko_tweets->get;
async {
my $orig_tweet_id = $tweet->{id_str};
my $reply_text = create_reply_text($tweet);
my $target = $tweet->{from_user};
$logger->info(
sprintf(
'[reply] Reply for %s (id = %s, text = "%s", tweeted at %s)'
. ' is scheduled.',
$target,
$orig_tweet_id,
$tweet->{text},
$tweet->{created_at},
)
);
sleep_thread(10 * 60);
$twitter->post('statuses/update' => +{
in_reply_to_status_id => $orig_tweet_id,
status => $reply_text,
}, Coro::rouse_cb);
my ($header, $res, $reason) = Coro::rouse_wait;
if ($header->{Status} eq '200') {
$logger->info(sprintf('[reply] Replied to %s.', $target));
} else {
$logger->warn(
sprintf(
'[reply] Failed to reply to %s'
. ' (in_reply_to_status_id = %s, text = "%s") : %s.',
$target,
$orig_tweet_id,
$reply_text,
$reason
)
);
}
};
}
};
AE::cv->recv; # Infinite loop.
sub create_reply_text {
my $tweet = shift;
my $tmpl = '@!user が "!tweet" とつぶやいてから10分経った。うんこは有意義だったか?';
my $orig_tweet = $tweet->{text};
my $text = create_reply_text1($tmpl, $tweet->{from_user}, $orig_tweet);
if (length($text) > 140) {
substr($orig_tweet, 140 - length($text) - 1) = '…';
$text = create_reply_text1($tmpl, $tweet->{from_user}, $orig_tweet);
}
return $text;
}
sub create_reply_text1 {
my ($tmpl, $screen_name, $orig_tweet) = @_;
$tmpl =~ s/!user/$screen_name/;
$tmpl =~ s/!tweet/$orig_tweet/;
return $tmpl;
}
sub is_unko_tweet {
my $tweet = shift;
return if $tweet->{to_user};
return if $tweet->{text} =~ /\bRT\b/;
1;
}
sub modify_message {
my %args = @_;
sprintf('[%d][%s]%s', time, $args{level}, $args{message});
}
sub search_unko_tweets {
my ($twitter, $rpp, $since_id) = @_;
$twitter->get(
search => +{
locale => 'ja',
q => 'うんこなう',
result_type => 'recent',
rpp => $rpp,
since_id => $since_id,
},
Coro::rouse_cb
);
Coro::rouse_wait;
}
sub sleep_thread {
my $sec = shift;
my $timeout = Coro::Timer::timeout($sec);
schedule until $timeout;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment