Skip to content

Commit

Permalink
Add the script to git
Browse files Browse the repository at this point in the history
Perl has so much whipitupitude!
  • Loading branch information
afresh1 committed Jul 8, 2012
1 parent 2a45ec5 commit f1bbfe2
Showing 1 changed file with 337 additions and 0 deletions.
337 changes: 337 additions & 0 deletions commits-to-twitter.pl
@@ -0,0 +1,337 @@
#!/usr/bin/perl
########################################################################
# Copyright (c) 2012 Andrew Fresh <andrew@afresh1.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
########################################################################
use strict;
use warnings;
use 5.010;

use DB_File;
use File::Basename;
use File::ChangeNotify;
use File::Find;
use Net::Twitter;

my $seen_file = $ENV{HOME} . '/.tweeted_changes';
my $auth_file = $ENV{HOME} . '/.auth_tokens';

my %accounts = (
cvs => 'openbsd_cvs',
src => 'openbsd_src',
ports => 'openbsd_ports',
xenocara => 'openbsd_xenocar',
www => 'openbsd_www',
);

# Login to twitter
foreach my $key ( sort keys %accounts ) {
my $account = $accounts{$key};
get_twitter_account($account);
}

my @dirs = (
'Maildir/.lists.openbsd.source-changes/',
'Maildir/.lists.openbsd.ports-changes/',
);

find( sub { check_message($_) }, @dirs );

my $watcher
= File::ChangeNotify->instantiate_watcher( directories => \@dirs, );
while ( my @events = $watcher->wait_for_events() ) {
foreach my $event (@events) {
next unless $event->type eq 'create';
check_message( $event->path );
}
}

sub check_message {
my ($file) = @_;
state $seen = load_seen();

my $commit = parse_commit($file);
return unless $commit;
return unless $commit->{id};

return if $seen->{ $commit->{id} };

my ( $message, $params ) = make_tweet($commit);
tweet( $message, $params );

if ( $params->{who} ne 'openbsd_cvs' ) {
tweet( shorten( $commit->{'Module name'} . ': ' . $message ),
{ %{$params}, who => 'openbsd_cvs' } );
}
$seen->{ $commit->{id} } = time;
sync_seen();
}

sub account_for {
my ($module) = @_;
return $accounts{$module} || 'openbsd_cvs';
}

sub change_for {
my ($commit) = @_;
my %changes;
my @dirs;
my @files;

foreach my $key ( keys %{$commit} ) {
if ( $key =~ /^(\w+)\s+files$/ ) {
$changes{ lc $1 }++;
foreach my $dir ( keys %{ $commit->{$key} } ) {
push @dirs, $dir;
push @files, @{ $commit->{$key}->{$dir} };
}
}
}

# Put them shortest first
@dirs = sort { length $a <=> length $b } @dirs;

my $match = shift @dirs;

unless (@dirs) {
my $base = $match;
if ( $base eq '.' ) { $base = '' }
else { $base .= '/' }

@dirs = sort { length $a <=> length $b }
map { $base . $_ } @files;
$match = shift @dirs;
}

foreach my $dir (@dirs) {
chop $match while $dir !~ /^\Q$match/;
}

$match =~ s{/+$}{}; # one less char most likely
$match ||= 'many things';

my @changes = keys %changes;
my $change = @changes == 1 ? $changes[0] : 'changed';

return "$change $match";
}

sub make_tweet {
my ($commit) = @_;
my %params = ( who => account_for( $commit->{'Module name'} ), );

my $by = $commit->{'Changes by'};
$by =~ s/\@.*$/\@/;

my $change = change_for($commit);

my $message = "$by $change: " . $commit->{'Log message'};

return shorten($message), \%params;
}

sub shorten {
my ($message) = @_;
if ( length $message > 140 ) {
$message =~ s/^(.{137}).*/$1/ms;
$message =~ s/\s+$//ms;
$message .= '...';
}
return $message;
}

sub tweet {
my ( $message, $params ) = @_;

say "Tweeting $message";
eval { get_twitter_account( $params->{who} )->update($message) };
if ($@) {
warn $@;
return 0;
}
return 1;
}

sub parse_commit {
my ($file) = @_;
return {} unless -f $file;

my %commit;

my $in = 'HEADER';
open my $fh, '<', $file or die $!;
my $key = '';
my $dir = '';
while (<$fh>) {
chomp;

if ( $in eq 'HEADER' ) {
if (/^Message-ID:\s+(.+?)\s*$/i) { $commit{id} = $1 }
unless ($_) { $in = 'BODY' }
next;
}

if (/(CVSROOT|Module name|Changes by):\s+(.*)$/) {
$commit{$1} = $2;
next;
}
return unless $commit{CVSROOT}; # first thing should be CVSROOT

if (/^(\w+ files|Log message):/) {
$key = $1;
next;
}

if ($key) {
if ( $key eq 'Log message' ) {
$commit{$key} = $_;
$commit{$key} .= $_ while <$fh>;
}
else {
chomp;
s/^\s+//;
unless ($_) {
$key = '';
next;
}

my (@files) = split /\s*:\s+/;
$dir = shift @files if @files > 1;
@files = map {split} @files;
next unless $dir;

push @{ $commit{$key}{$dir} }, @files;
}
}
}
close $fh;

if ( my $changes = $commit{'Changes by'} ) {
my ( $who, $when ) = split /\s+/, $changes, 2;
$commit{'Changes by'} = $who;
$commit{'Changes on'} = $when;
}

$commit{'Log message'} =~ s/\s+$//ms;

return \%commit;
}

{
my $X;

sub load_seen {
$X = tie my %seen, 'DB_File', $seen_file or die;
return \%seen;
}

sub sync_seen {
$X->sync;
}

}

{
my %tokens;

sub get_access_tokens {
my ( $account, $nt ) = @_;

return $tokens{$account} if exists $tokens{$account};

open my $fh, '<', $auth_file or die $!;
while (<$fh>) {
chomp;
my ($account_from_file, $access_token, $access_token_secret,
$user_id, $screen_name
) = split /\s+/;

if ( $account_from_file eq 'consumer' ) {
$tokens{$account_from_file} = {
consumer_key => $access_token,
consumer_secret => $access_token_secret,
};
}
else {
$tokens{$account_from_file} = {
access_token => $access_token,
access_token_secret => $access_token_secret,
user_id => $user_id,
screen_name => $screen_name,
};
}
}
close $fh;
return $tokens{$account} if exists $tokens{$account};

return unless $nt;

my $auth_url = $nt->get_authorization_url;
print
" Authorize $account for this application at:\n $auth_url\nThen, enter the PIN# provided to continue ";

my $pin = <STDIN>; # wait for input
chomp $pin;

# request_access_token stores the tokens in $nt AND returns them
my ( $access_token, $access_token_secret, $user_id, $screen_name )
= $nt->request_access_token( verifier => $pin );

# save the access tokens
$tokens{$account} = {
access_token => $access_token,
access_token_secret => $access_token_secret,
user_id => $user_id,
screen_name => $screen_name,
};

save_access_tokens();

return $tokens{$account};
}

sub save_access_tokens {
open my $fh;
foreach my $key ( sort keys %tokens ) {
my @keys
= $key eq 'consumer'
? qw( consumer_key consumer_secret )
: qw( access_token access_token_secret user_id screen_name );
say join "\t", $key, @{ $tokens{$key} }{@keys};
}
close $fh;
}
}

sub get_twitter_account {
my ($account) = @_;

my $consumer_tokens = get_access_tokens('consumer');

my $nt = Net::Twitter->new(
traits => [qw/API::REST OAuth/],
%{$consumer_tokens}
);

my $tokens = get_access_tokens( $account, $nt );

$nt->access_token( $tokens->{access_token} );
$nt->access_token_secret( $tokens->{access_token_secret} );

#my $status = $nt->user_timeline( { count => 1 } );
#print Dumper $status;
#print Dumper $nt;

return $nt;
}

0 comments on commit f1bbfe2

Please sign in to comment.