Cookie Notice

As far as I know, and as far as I remember, nothing in this page does anything with Cookies.
Showing posts sorted by relevance for query twitter. Sort by date Show all posts
Showing posts sorted by relevance for query twitter. Sort by date Show all posts

2012/02/03

A little bit on OAuth and Net::Twitter on the Command Line

I have a Twitter application up on Github. I quote:

#!/usr/bin/perl

# largely taken verbatim from
# http://search.cpan.org/dist/Net-Twitter/lib/Net/Twitter/Role/OAuth.pm

# Next step is to get the keys and secrets to a config.

# you need to get your own access token and secret (which identifies you
# as a developer or application) and consumer key and secret (which
# identifies you as a Twitter user). You cannot use mine.

use 5.010 ;
use strict ;
use IO::Interactive qw{ interactive } ;
use Net::Twitter ;
use Carp ;

my $status = join ' ', @ARGV ;
if ( length $status < 1 ) {
    while ( <stdin> ) {
        $status .= $_ ;
        }
    chomp $status ;
    }

if ( length $status > 140 ) {
    say { interactive } 'Too long' ;
    say { interactive } length $status ;
    exit ;
    }
if ( length $status < 1 ) {
    say { interactive } 'No content' ;
    say { interactive } length $status ;
    exit ;
    }

say $status ;

# GET key and secret from http://twitter.com/apps
my $twit = Net::Twitter->new(
        traits          => [ 'API::REST', 'OAuth' ],
        consumer_key    => 'consumer_key' ,   #GET YOUR OWN
        consumer_secret => 'consumer_secret', #GET YOUR OWN
        ) ;

# You'll save the token and secret in cookie, config file or session database
my ( $access_token, $access_token_secret ) ;
( $access_token, $access_token_secret ) = restore_tokens() ;

if ( $access_token && $access_token_secret ) {
    $twit->access_token( $access_token ) ;
    $twit->access_token_secret( $access_token_secret ) ;
    }

unless ( $twit->authorized ) {

    # You have no auth token
    # go to the auth website.
    # they'll ask you if you wanna do this, then give you a PIN
    # input it here and it'll register you.
    # then save your token vals.

    say "Authorize this app at ", $twit->get_authorization_url, ' and enter the PIN#' ;
    my $pin = <stdin> ;    # wait for input
    chomp $pin ;
    my ( $access_token, $access_token_secret, $user_id, $screen_name ) =
      $twit->request_access_token( verifier => $pin ) ;
    save_tokens( $access_token, $access_token_secret ) ;    # if necessary
    }

if ( $twit->update( $status ) ) {
    say { interactive } 'OK' ;
    }
else {
    say { interactive } 'FAIL' ;
    }

#========= ========= ========= ========= ========= ========= =========

# Docs-suggested
sub restore_tokens {
    my $access_token = 'token' ;            #GET YOUR OWN
    my $access_token_secret = 'secret' ;    #GET YOUR OWN
    return $access_token, $access_token_secret ;
    }

sub save_tokens {
    my ( $access_token, $access_token_secret ) = @_ ;
    say 'access_token: ' . $access_token ;
    say 'access_token_secret: ' . $access_token_secret ;
    return 1 ;
    }

I mention this, in part, because I'm going back to the code so I can automate tweets for @PurduePM, the Twitter feed of the Purdue Perl Mongers, which I am taking over.

I remember that, when I wrote this and first tried to use it, it frustrated and confused me. When I tried to dust it off this afternoon, I found it to be downright easy. I wiped the access tokens and ran the code. It hit the if statement, gave you the authentication URL and waited for a PIN. Given that, it "saved" the tokens (by which I mean "wrote to STDOUT". I then put the access token and speed. The means I have for saving and restoring tokens, which is less than minimal, could easily be improved. Thing is, I have no means within the code to distinguish between users. The usage is twitter.pl This is a tweet or echo This is another tweet | twitter.pl, with the entirety of @ARGV being concatenated into the message, so it's entirely within the code whether we're determining which account is being used.

But, it strikes me I could make aliases. Alias twitter to "twitter.pl jacobydave " and shift the username from @ARGV. (BTW, I'm on Twitter as @JacobyDave. Follow me.) Then, alias twit_ppm or something to "twitter.pl purduepm " and so on.

I really got to the "it works, so can I please get to the next thing?" stage and stopped playing with it, before I got to the really interesting point. I'm certainly beginning to appreciate how useful and cool OAuth is. I'll attempt to come up with the next better thing, as well as put together the means to store the keys in an external file. Remember, you'll have to get to the Twitter Developer page and create an app to get the crucial consumer key if you want to use this code. If I let you have mine, and someone starts using it to spam a lot, Twitter might revoke the key to protect itself. 

2017/07/25

Sentimentalizing Twitter: First Pass

It started here.

I couldn't leave it there.

I can write a tool that goes through all old tweets and deletes ones that don't pass criteria, but I prefer to get out ahead of the issue and leave the record as it is.

And, as a later pass, one can pull a corpus, use a module like Algorithm::NaiveBayes and make your own classifier, rather than using Microsoft Research's Text Analytics API or another service. I was somewhere along that process when the hosting died, so I'm not bringing it here.

I was kinda under the thrall of Building Maintainable Software, or at least the first few chapters of it, so I did a few things differently in order to get their functions less than 15 lines, but the send_tweet function didn't get the passes it'd need, and I could probably give check_status some love, or perhaps even roll it into something like WebService::Microsoft::TextAnalytics. In the mean time, this should allow you to always tweet on the bright side of life.

#!/usr/bin/env perl

use feature qw{ postderef say signatures } ;
use strict ;
use warnings ;
use utf8 ;

no warnings qw{ experimental::postderef experimental::signatures } ;

use Carp ;
use Getopt::Long ;
use IO::Interactive qw{ interactive } ;
use JSON ;
use LWP::UserAgent ;
use Net::Twitter ;
use YAML qw{ LoadFile } ;

my $options = options() ;
my $config  = config() ;

if ( check_status( $options->{ status }, $config ) >= 0.5 ) {
    send_tweet( $options, $config ) ;
    }
else { say qq{Blocked due to negative vibes, man.} }
exit ;

sub send_tweet ( $options, $config ) {
    my $twit = Net::Twitter->new(
        traits          => [ qw/API::RESTv1_1/ ],
        consumer_key    => $config->{ twitter }{ consumer_key },
        consumer_secret => $config->{ twitter }{ consumer_secret },
        ssl             => 1,
        ) ;
    my $tokens = $config->{ twitter }{ tokens }{ $options->{ username } } ;
    if (   $tokens->{ access_token }
        && $tokens->{ access_token_secret } ) {
        $twit->access_token( $tokens->{ access_token } ) ;
        $twit->access_token_secret( $tokens->{ access_token_secret } ) ;
        }
    if ( $twit->authorized ) {
        if ( $twit->update( $options->{ status } ) ) {
            say { interactive } $options->{ status } ;
            }
        else {
            say { interactive } 'FAILED TO TWEET' ;
            }
        }
    else {
        croak( "Not Authorized" ) ;
        }
    }

sub check_status ( $status, $config ) {
    my $j   = JSON->new->canonical->pretty ;
    my $key = $config->{ microsoft }{ text_analytics }{ key } ;
    my $id  = 'tweet_' . time ;
    my $object ;
    push @{ $object->{ documents } },
        {
        language => 'EN',
        text     => $status,
        id       => $id,
        } ;
    my $json    = $j->encode( $object ) ;
    my $api     = 'https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment' ;
    my $agent   = LWP::UserAgent->new ;
    my $request = HTTP::Request->new( POST => $api ) ;
    $request->header( 'Ocp-Apim-Subscription-Key' => $key ) ;
    $request->content( $json ) ;
    my $response = $agent->request( $request ) ;

    if ( $response->is_success ) {
        my $out = decode_json $response->content ;
        my $doc = $out->{ documents }[ 0 ] ;
        return $doc->{ score } ;
        }
    else {
        croak( $response->status_line ) ;
        }
    return 1 ;
    }

sub config () {
    my $config ;
    $config->{ twitter }   = LoadFile( join '/', $ENV{ HOME }, '.twitter.cnf' ) ;
    $config->{ microsoft } = LoadFile( join '/', $ENV{ HOME }, '.microsoft.yml' ) ;
    return $config ;
    }

sub options () {
    my $options ;
    GetOptions(
        'help'       => \$options->{ help },
        'username=s' => \$options->{ username },
        'status=s'   => \$options->{ status },
        ) ;
    show_usage( $options ) ;
    return $options ;
    }

sub show_usage ($options) {
    if (   $options->{ help }
        || !$options->{ username }
        || !$options->{ status } ) {
        say { interactive } <<'HELP';
Only Positive Tweets -- Does text analysis of content before tweeting

    -u  user        Twitter screen name (required)
    -s  status      Status to be tweeted (required)
    -h  help        This screen
HELP
        exit ;
        }
    }
__DATA__

.microsoft.yml looks like this
---
text_analytics:
    key: GENERATED_BY_MICROSOFT

.twitter.cnf looks like this

---
consumer_key: GO_TO_DEV.TWITTER.COM
consumer_secret: GO_TO_DEV.TWITTER.COM
tokens:
    your_username:
        access_token: TIED_TO_YOU_AS_USER_NOT_DEV
        access_token_secret:TIED_TO_YOU_AS_USER_NOT_DEV

I cover access_tokens in https://varlogrant.blogspot.com/2016/07/nettwitter-cookbook-how-to-tweet.html


2016/10/28

Using the Symbol Table: "Help"?

I've been looking at command-line code for both fun and work. I know I can have one module handle just the interface, and have the module where the functionality happens pass the functionality along.

#!/usr/bin/env perl

use feature qw'say state' ;
use strict ;
use warnings ;
use utf8 ;

my $w = Wit->new( @ARGV ) ;
$w->run() ;

package Wit ;
use lib '/home/jacoby/lib' ;
use base 'Witter' ;
use Witter::Twitter ;

1;

package Witter ;

# highly adapted from perlbrew.

use feature qw{ say } ;
use strict ;
use warnings ;

sub new {
    my ( $class, @argv ) = @_ ;
    my $self ;
    $self->{foo}  = 'bar' ;
    $self->{args} = [] ;
    if (@argv) {
        $self->{args} = \@argv ;
        }
    return bless $self, $class ;
    }

sub run {
    my ($self) = @_ ;
    $self->run_command( $self->{args} ) ;
    }

sub run_command {
    my ( $self, $args ) = @_ ;

    if (   scalar @$args == 0
        || lc $args->[0] eq 'help'
        || $self->{help} ) {
        $self->help(@$args) ;
        exit ;
        }

    if ( lc $args->[0] eq 'commands' ) {
        say join "\n\t", '', $self->commands() ;
        exit ;
        }

    my $command = $args->[0] ;

    my $s = $self->can("twitter_$command") ;
    unless ($s) {
        $command =~ y/-/_/ ;
        $s = $self->can("twitter_$command") ;
        }

    unless ($s) {

        my @commands = $self->find_similar_commands($command) ;
        if ( @commands > 1 ) {
            @commands = map { '    ' . $_ } @commands ;
            die
                "Unknown command: `$command`. Did you mean one of the following?\n"
                . join( "\n", @commands )
                . "\n" ;
            }
        elsif ( @commands == 1 ) {
            die "Unknown command: `$command`. Did you mean `$commands[0]`?\n"
                ;
            }
        else {
            die "Unknown command: `$command`. Typo?\n" ;
            }
        }

    unless ( 'CODE' eq ref $s ) { say 'Not a valid command' ; exit ; }

    $self->$s(@$args) ;
    }

sub help {
    my ($self,$me,@args) = @_ ;
    say 'HELP!' ;
    say join "\t", @args;
    }

sub commands {
    my ($self) = @_ ;
    my @commands ;

    my $package = ref $self ? ref $self : $self ;
    my $symtable = do {
        no strict 'refs' ;
        \%{ $package . '::' } ;
        } ;

    foreach my $sym ( sort keys %$symtable ) {
        if ( $sym =~ /^twitter_/ ) {
            my $glob = $symtable->{$sym} ;
            if ( defined *$glob{CODE} ) {
                $sym =~ s/^twitter_// ;
                $sym =~ s/_/-/g ;
                push @commands, $sym ;
                }
            }
        }

    return @commands ;
    }

# Some functions removed for sake of brevity


package Witter::Twitter ;

use strict ;
use feature qw{ say state } ;
use warnings FATAL => 'all' ;

use Exporter qw{import} ;
use Net::Twitter ;
use JSON::XS ;


our $VERSION = 0.1 ;

our @EXPORT ;
for my $entry ( keys %Witter::Twitter:: ) {
    next if $entry !~ /^twitter_/mxs ;
    push @EXPORT, $entry ;
    }

sub twitter_foo {
    my ( $self, @args ) = @_ ;
    say "foo" ;
    say join '|', @args ;
    }
1 ;


And the above works when called as below.
jacoby@oz 13:49 60°F 51.24,-112.49 ~ 
$ ./witter 
HELP!


jacoby@oz 13:52 60°F 51.25,-94.51 ~ 
$ ./witter help 
HELP!


jacoby@oz 13:53 60°F 50.59,-88.64 ~ 
$ ./witter commands

 foo

jacoby@oz 13:53 60°F 50.59,-88.64 ~ 
$ ./witter help foo
HELP!
foo

jacoby@oz 13:53 60°F 50.59,-88.64 ~ 
$ ./witter foo
foo
foo

jacoby@oz 13:53 60°F 50.59,-88.64 ~ 
$ ./witter moo
Unknown command: `moo`. Did you mean `foo`?

In the above example, I'm just doing the one add-on module, Witter::Twitter and one function, Witter::Twitter::foo, but clearly, I would want it open-ended, so that if someone wanted to add Witter::Facebook, all the information about the Facebook functions would be in that module.

Then, of course, I would have to use another prefix than twitter_, but we'll leave that, and ensuring that modules don't step on each others' names, to another day.

The part that concerns me is help. Especially help foo. It should be part of the the module it's in; If Witter::Twitter is the module with foo(), only it should be expected to know about foo().

But how to communicate it? I'm flashing on our %docs and $docs{foo}= 'This is foo, silly' but the point of the whole thing is to allow the addition of modules that the initial module doesn't know about, and it would require knowing to look for %Witter::Twitter::docs.

I suppose adding a docs_ function that looks like this.
sub docs_foo {
    return q{
    This explains the use of the 'foo' command 

...
};
END
}


I'm diving into this in part because I have code that uses basically this code, and I need to add functionality to it, and while I'm in there, I might as well make user documentation better. Or even possible.

I'm also parallel-inspired by looking at a Perl project built on and using old Perl ("require 5.005") and recent blog posts about Linus Torvalds and "Good Taste". There's something tasteful about being able to add use Your::Module and nothing else to code, but if the best I can do is knowledge that there's a foo command, with no sense of what it does, that seems like the kind of thing that Linus would rightfully curse me for.

Is there a better way of doing things like this? I recall there being interesting things in Net::Tumblr that would require me to step up and learn Moose or the like. This is yet another important step toward me becoming a better and more tasteful programmer, but not germane to today's ranting.

2016/07/27

Net::Twitter Cookbook: How to Tweet



The first line between Twitter Use and Twitter Obsession is TweetDeck. That's when the update-on-demand single-thread of the web page gives way to multiple constantly-updated streams of the stream-of-consciousness ramblings of the Internet.

That's the first line.

The second line between Twitter use and Twitter obsession is when you want to automate the work. If you're an R person, that's twitteR. If you work in Python, that's tweepy.

And, if you're like me, and you normally use Perl, we're talking Net::Twitter.

What follows is the simplest possible Net::Twitter program.

#!/usr/bin/env perl
use feature qw{ say } ;
use strict ;
use warnings ;
use Net::Twitter ;

# the consumer key and secret identify you as a service. 
# you register your service at https://apps.twitter.com/
# and receive the key and secret

# you really don't want to have these written into your script

my $consumer_key    = 'ckckckckckckckckckckck' ;
my $consumer_secret = 'cscscscscscscscscscscscscscscscscscscscs' ;

my $twit = Net::Twitter->new(
    traits          => [qw/API::RESTv1_1/],
    consumer_key    => $consumer_key,
    consumer_secret => $consumer_secret,
    ssl             => 1,
    ) ;

# the access token and secret identify you as a user.
# the registration process takes place below.
# the first time you run this program, you will not be authorized,
# and the program will give you a URL to open in a browser where
# you are already logged into twitter.

# you really don't want to have these written into your script

my $access_token = '1111111111-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ;
my $access_token_secret = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' ;

$twit->access_token($access_token) ;
$twit->access_token_secret($access_token_secret) ;

# everything interesting will occur inside this if statement
if ( $twit->authorized ) {
    if ( $twit->update('Hello World!') ) {
        say 'It worked!' ;
        }
    else {
        say 'Fail' ;
        }
    }
else {
    # You have no auth token
    # go to the auth website.
    # they'll ask you if you wanna do this, then give you a PIN
    # input it here and it'll register you.
    # then save your token vals.

    say "Authorize this app at ", $twit->get_authorization_url,
        ' and enter the PIN#' ;
    my $pin = <stdin> ;    # wait for input
    chomp $pin ;
    my ( $access_token, $access_token_secret, $user_id, $screen_name ) =
        $twit->request_access_token( verifier => $pin ) ;

    say 'The following lines need to be copied and pasted above' ;
    say $access_token ;
    say $access_token_secret ;
    }

Again, this is as simple as we can reasonably do, without pulling the keys into a separate file, which I, again, strongly recommend you do. (I personally use YAML as the way I store and restore data such as access tokens and consumer keys. I will demonstrate that in a later post.)

2010/09/17

Thoughts about Passwords and their replacments

As I discussed previously, there's a rising use of OAuth as a mechanism to do authentication online. I don't really grok it, but I get it now.

Alice wants to use Bob's website to send her tweets to Twitter. Bob sets up with Twitter to get his Consumer Key and Consumer Secret. This uniquely identifies SuperCoolSiteOfBob as a Twitter client. Alice goes to Bob's site and starts to connect. This means a jump to Twitter to allow/deny SuperCoolSiteOfBob to do what it needs to to Alice's Twitter feed, in the form of giving Bob an Access Token and Secret to Alice's account. Alice can check her settings and see SuperCoolSiteOfBob and all the other folks she let have access to her Twitter account, and Twitter can decide that SuperCoolSiteOfBob is doing the wrong thing and block it. All without giving out the main keys to the city, Alice's Twitter password.

This is a good thing.

If anyone asks you for your login and password, who isn't the one and only service you're trying to access, while you're trying to access, treat them like they are trying to bankrupt you, take all your belongings, sell your children into slavery and do donuts in the Payless parking lot until your tires burst and your rims spark. Go to a site and have it say It will be much cooler if you could talk to all your friends, so give us your Gmail login and password and we'll see if they're already on and I read it as saying I'd like to f*** your wife or something even more offensive. Here's a several-year-old discussion of the problem and why OAuth is needed.

Let's sidestep for a moment. There's DVDs. There's basic encryption so not just anybody can play (meaning rip) DVDs. Yet, you want people to play (meaning watch) DVDs, and here, yes, you want just anybody to do so. Children watching Spongebob Squarepants. The elderly watching I Love Lucy. The stoned watching Spongebob Squarepants. Just put the disc in and go. So, everybody needed the key, but you needed to hide the key. And, people wanted the key so they can rip DVDs. And they got it.

Now, let's switch from SuperCoolSiteOfBob to SuperCoolAppOfBob. This is running on Alice's desktop or her phone. Or, Marcia's. (I mentally think of Alice and Bob as Alice the house keeper and Bobby of the Brady Bunch. Eve the eavesdropper is of course Eve Plumb, who played Jan. I once made mugshot-looking images with the protect-the-innocent black bars across their eyes.) Marcia can easily do what they did to DVDs to Bob's app and come up with a Twitter-bashing tool that claims to be Bob's app, eventually forcing Twitter to disavow and denounce Bob, meaning that Alice has to download another app to do her tweeting. Which is bad.

I do have a thought here. Bob as developer needs to have a Consumer Key and Secret (from here on out referred to as Consumer Key) in order to test and know his code works. Bob as website needs the key and can keep it secure. Bob as software vendor doesn't need to distribute his Consumer Key with his software, as long as Alice can easily get one from Twitter, and the process is not too hard. Why don't we go with this?

2010/09/01

Surviving the Twitpocalypse

The 2010 Twitpocalypse has come. Buffy didn't save us this time, I guess. And what that means is that Twitter is no longer doing basic authentication for anything but their home page. So, if you have code like this:
my $twit = Net::Twitter->new(
    username => 'varlo',
    password => 'ISoLoveEdward' , #No Jacob
    clientname => $client ,
    ) ;
You're kinda screwed.

It's using OAuth now. I don't fully grok OAuth, so if you want the full deal, you should Google it and all, but in a nutshell, there is a key and secret combination that uniquely identifies my application. You use this application and it says "I don't see a user key" and sends you to Twitter. You get a user key and value, and you can use that user key and secret in conjunction with the app key and secret until you decide to go to Twitter and disassociate your user key with the app key.

This means that at no point does the application have your password, the same password that you might use for your pizza delivery, bank, email, etc. Until I began to understand OAuth, I thought it was a PITA, but I now think it's at least 70% win.

I have discussed Net::Twitter before, so most of the following code has been up before. My preferred way of handling a status is to join STDIN or ARGV with a space, but there should be enough here for you to adapt it to your workflow.

#!/usr/bin/perl

# largely taken verbatim from
# http://search.cpan.org/dist/Net-Twitter/lib/Net/Twitter/Role/OAuth.pm

# Next step is to get the keys and secrets to a config.

use 5.010 ;
use strict ;
use IO::Interactive qw{ interactive } ;
use Net::Twitter ;
use Carp ;

my $status = join ' ', @ARGV ;
if ( length $status < 1 ) {
    while (  ) {
        $status .= $_ ;
        }
    chomp $status ;
    }

if ( length $status > 140 ) {
    say { interactive } 'Too long' ;
    say { interactive } length $status ;
    exit ;
    }
if ( length $status < 1 ) {
    say { interactive } 'No content' ;
    say { interactive } length $status ;
    exit ;
    }

say $status ;

# GET key and secret from http://twitter.com/apps
my $twit = Net::Twitter->new( traits          => [ 'API::REST', 'OAuth' ],
                              consumer_key    => 'GetYorConsumerKeyFromTwitterDotComSlashApps',
                              consumer_secret => 'GetYorConsumerSecretFromTwitterDotComSlashApps',
                              ) ;

# You'll save the token and secret in cookie, config file or session database
my ( $access_token, $access_token_secret ) ;
( $access_token, $access_token_secret ) = restore_tokens() ;

if ( $access_token && $access_token_secret ) {
    $twit->access_token( $access_token ) ;
    $twit->access_token_secret( $access_token_secret ) ;
    }

unless ( $twit->authorized ) {

    # You have no auth token
    # go to the auth website.
    # they'll ask you if you wanna do this, then give you a PIN
    # input it here and it'll register you.
    # then save your token vals.

    say "Authorize this app at ", $twit->get_authorization_url, ' and enter the PIN#' ;
    my $pin =  ;    # wait for input
    chomp $pin ;
    my ( $access_token, $access_token_secret, $user_id, $screen_name ) =
      $twit->request_access_token( verifier => $pin ) ;
    save_tokens( $access_token, $access_token_secret ) ;    # if necessary
    }

if ( $twit->update( $status ) ) {
    say { interactive } 'OK' ;
    }
else {
    say { interactive } 'FAIL' ;
    }

#========= ========= ========= ========= ========= ========= =========

# Docs-suggested
sub restore_tokens {
    my $access_token        = 'GetYrOwnAccessToken' ;
    my $access_token_secret = 'GetYrOwnAccessTokenSecret' ;
    return $access_token, $access_token_secret ;
    }

sub save_tokens {
    my ( $access_token, $access_token_secret ) = @_ ;
    say 'access_token: ' . $access_token ;
    say 'access_token_secret: ' . $access_token_secret ;
    return 1 ;
    }
And if this has helped anyone drive their pet dingo and XB Falcon into the post-twitpocalyptic wasteland, remember, I'm just here for the gasoline.

2016/08/02

Net::Twitter Cookbook: How I tweet, plus

Previously, I wrote a post showing the basics on how to send a tweet using Perl and Net::Twitter. I showed the easiest you can do it.

Below is the code that I use most often. It is a command-line tool, where it's used something along the lines of named_twitter.pl jacobydave text of my tweet. Except, that's not how I type it, thanks to my .alias file. I have twitter aliased to '~/bin/named_twitter jacobydave ' and normally tweet like twitter this is how I tweet.

This isn't to say I never automate tweets; I certainly do. But it is a rare part of what I do with the Twitter API and Net::Twitter. I will dive deeper into issues with tweeting and direct messages, both in a technical and social matter, in a later post.

But I said that you have the consumer key and secret, which identify you as a service, and the access token and secret, which identify you as a user of the service. In the above code sample, I use YAML to store this data, but you could use JSON, Sqlite or anything else to store it. Among other benefits, you can put your code into GitHub, as I did above, without exposing anything.

As I proceed, I will assume that tokens are handled somehow and proceed directly to the cool part.

Again, you get the consumer key by going to apps.twitter.com and creating a new app.


You log into apps.twitter.com with your Twitter account, click "Create New App" and fill in the details. When I created my three, you had to specifically choose "can send DMs" specifically, so if you're creating apps to follow along, I do suggest you allow yourself that option.

2016/08/06

Net::Twitter Cookbook: Images and Profiles

I've covered how you handle keys and authenticating with Twitter previously, so look into those for more information as we go ahead with sending tweets!

There was a time when Twitter was just text, so you could just send a link to an image. There were sites like TwitPix that hosted images for you, but eventually Twitter developed the ability to host media.

    my $media ;
    push @$media, $config->{ file } ;
    push @$media, 'icon.' . $suffix ;
    if ( $twit->update_with_media( $status , $media ) ) {
        say 'OK' ;
        }

There are four ways you could define the media:

    $media = [ 'path to media' ] ;
    $media = [ 'path to media', 'replacment filename' ] ;
    $media = [ 'path to media', 'replacment filename', 
            Content-Type => mime/type' ] ;
    $media = [  undef , 'replacment filename', 
            Content-Type => mime/type', 'raw media data'] ;


I use the second in order to specify the file name that isn't whatever long and random filename it has. The module has ways to guess the correct mime type, but you can specify to avoid that. The last one, starting with undef, allows you to create or modify an image with Image::Magick or whatever you choose and tweet it without having to involve the filesystem at all.

A few words about what we mean by media. We mean images or video; no audio file option. By images, we mean PNG, JPG, GIF or WEBP. No SVG, and less that 5MB as of this writing. (Check https://dev.twitter.com/ for more info later.)

Video has other constraints, but I don't tweet just video often, if ever. I generally put it to YouTube and post the link.

There's a couple ways you can have more fun with images on Twitter. Those would be with your profile image, the square image that sits next to your tweet, and the profile banner, the landscape image that shows up at the top of your page.

They work like the above, handling media the same way.

    my $image ;
    push @$image, $config->{ file } ;
    push @$image, 'icon.' . $suffix ;
    if ( $twit->update_profile_image( $image ) ) {
        say 'OK' ;
        }

    my $banner ;
    push @$banner, $config->{ file } ;
    push @$banner, 'icon.' . $suffix ;
    if ( $twit->update_profile_banner( $banner ) ) {
        say 'OK' ;
        }


Profile images are square, and are converted to square if your initial image is not square. For both, JPG, GIF or PNG are allowed and are converted upon upload. If you try to upload an animated GIF, they will use the first frame. This can make your profile image less fun, but if you have a feed full of pictures that throb and spin, that can make you feel seasick.

And, since we're hitting profile-related issues, perhaps I should handle your profiles and we can get this behind us.

Changing your username, going from @jacobydave to something else, is doable but not via the API. It isn't a thing you can or should be doing with an API. You can change other details, however. You can change your name ("Dave Jacoby"), description ("Guy blogging about Perl's Net::Twitter library. Thought Leader. Foodie."), website ("http://varlogrant.blogspot.com/") and location ("Dark Side of the Moon").

    use Getopt::Long ;

    my $config ;
    GetOptions(
        'description=s' => \$config->{ description },
        'location=s'    => \$config->{ location },
        'name=s'        => \$config->{ name },
        'web=s'         => \$config->{ url },
        ) ;

    my $params ; for my $p ( qw{ name url location description } ) {
    $params->{ $p } = $config->{ $p } if $config->{ $p } ; }
    $params->{ include_entities } = 0 ;  $params->{ skip_status } = 1
    ;

    if ( $twit->update_profile( $params ) ) {
        say 'OK' ;
        }
    }


I *DO* have fun fun with location, setting it with a number of strings; "My Happy Place" on Saturday, "Never got the hang..." for Thursdays. You can set this to any string, and Twitter does nearly nothing with it. I'd suggest you ignore it or set it to something general or clever, and forget it.

Now that we've covered how you tweet and handle your identity a little, next time we'll start to get into relationships, which are the things that make a social network social.

2016/10/30

Net::Twitter Cookbook: Favorites and Followers

Favorites.

Also known as "Likes", they're an indication in Twitter that you approve of a status update. Most of the time, they're paired with retweets as signs by the audience to the author that the post is agreeable. Like digital applause.

This is all well and good, but it could be used for so much more, if you had more access and control over them.

So I did.

The first step is to collect them. There's an API to get them, and collecting them in bulk is easy. A problem is avoiding grabbing the same tweet twice.

# as before, the "boilerplate" can be found elsewhere in my blog.
use IO::Interactive qw{ interactive } ;
my $config ;
$config->{start} = 0 ;
$config->{end}   = 200 ;

for ( my $page = $config->{start}; $page <= $config->{end}; ++$page ) {
        say {interactive} qq{\tPAGE $page} ;
        my $r = $twit->favorites( { 
            page => $page ,
            count => 200 ,
            } ) ;
        last unless @$r ;

        # push @favs , @$r ;
        for my $fav (@$r) {
            if ( $config->{verbose} ) { 
                 say {interactive} handle_date( $fav->{created_at} ) 
                 }
            store_tweet( $config->{user}, $fav ) ;
            }
        sleep 60 * 3 ;    # five minutes
        }


Once I had a list of my tweets, one of the first things I did was use them to do "Follow Friday". If you know who you favorited over the last week, it's an easy thing to get a list of the usernames, count them and add them until you have reached the end of the list or 140 characters.

Then, as I started playing with APIs and wanted to write my own ones, I created an API to find ones containing a substring, like json or pizza or sleep. This way, I could begin to use a "favorite" as a bookmark.

(I won't show demo code, because I'm not happy or proud of the the code, which lives in a trailing-edge environment, and because it's more database-related than Twitter-focused.)

As an aside, I do not follow back. There are people who follow me who I have no interest in reading, and there are people I follow who care nothing about my output. In general, I treat Twitter as something between a large IRC client and an RSS reader, and I never expected nor wanted RSS feeds to track me.

But this can be a thing worth tracking, which you can do, without any storage, with the help of a list. Start with getting a list of those following you, those you follow, and the list of accounts (I almost wrote "people", but that isn't guaranteed) in your follows-me list. If they follow you and aren't in your list, add them. If they're in the list and you have started following them, take them out. If they're on the list and aren't following you, drop them. As long as you're not big-time (Twitter limits lists to 500 accounts), that should be enough to keep a Twitter list of accounts you're not following.

use List::Compare ;

    my $list = 'id num of your Twitter list';

    my $followers = $twit->followers_ids() ;
    my @followers = @{ $followers->{ids} } ;

    my $friends = $twit->friends_ids() ;
    my @friends = @{ $friends->{ids} } ;

    my @list = get_list_members( $twit, $list ) ;
    my %list = map { $_ => 1 } @list ;


    my $lc1 = List::Compare->new( \@friends,   \@followers ) ;
    my $lc2 = List::Compare->new( \@friends,   \@list ) ;
    my $lc3 = List::Compare->new( \@followers, \@list ) ;

    # if follows me and I don't follow, put in the list
    say {interactive} 'FOLLOWING ME' ;
    for my $id ( $lc1->get_complement ) {
        next if $list{$id} ;
        add_to_list( $twit, $list, $id ) ;
        }

    # if I follow, take off the list
    say {interactive} 'I FOLLOW' ;
    for my $id ( $lc2->get_intersection ) {
        drop_from_list( $twit, $list, $id ) ;
        }

    # if no longer following me, take off the list
    say {interactive} 'NOT FOLLOWING' ;
    for my $id ( $lc3->get_complement ) {
        drop_from_list( $twit, $list, $id ) ;
        }

#========= ========= ========= ========= ========= ========= =========
sub add_to_list {
    my ( $twit, $list, $id ) = @_ ;
    say STDERR qq{ADDING $id} ;
    eval { $twit->add_list_member(
            { list_id => $list, user_id => $id, } ) ; } ;
    if ($@) {
        warn $@->error ;
        }
    }

#========= ========= ========= ========= ========= ========= =========
sub drop_from_list {
    my ( $twit, $list, $id ) = @_ ;
    say STDERR qq{REMOVING $id} ;
    eval {
        $twit->delete_list_member( { list_id => $list, user_id => $id, } ) ;
        } ;
    if ($@) {
        warn $@->error ;
        }
    }



But are there any you should follow? Are there any posts in the the feed that you might "like"? What do you "like" anyway?

There's a way for us to get an idea of what you would like, which is your past likes. First, we must get, for comparison, a collection of what your Twitter feed is like normally. (I grab 200 posts an hour and store them. This looks and works exactly like my "grab favorites code", except I don't loop it.

    my $timeline = $twit->home_timeline( { count => 200 } ) ;

    for my $tweet (@$timeline) {
        my $id          = $tweet->{id} ;                          # twitter_id
        my $text        = $tweet->{text} ;                        # text
        my $created     = handle_date( $tweet->{created_at} ) ;   # created
        my $screen_name = $tweet->{user}->{screen_name} ;         # user id
        if ( $config->{verbose} ) {
            say {interactive} handle_date( $tweet->{created_at} );
            say {interactive} $text ;
            say {interactive} $created ;
            say {interactive} $screen_name ;
            say {interactive} '' ;
            }
        store_tweet( $config->{user}, $tweet ) ;
        # exit ;
        }


So, we have a body of tweets that you like, and a body of tweets that are a representative sample of what Twitter looks like to you. On to Algorithm::NaiveBayes!

use Algorithm::NaiveBayes ;
use IO::Interactive qw{ interactive } ;
use String::Tokenizer ;

my $list   = 'ID of your list';
my $nb     = train() ;
my @top    = read_list( $config, $nb , $list ) ;

say join ' ' , (scalar @top ), 'tweets' ;

for my $tweet (
    sort { $a->{analysis}->{fave} <=> $b->{analysis}->{fave} } @top ) {
    my $fav = int $tweet->{analysis}->{fave} * 100 ;
    say $tweet->{text} ;
    say $tweet->{user}->{screen_name} ;
    say $tweet->{gen_url} ;
    say $fav ;
    say '' ;
    }

exit ;

#========= ========= ========= ========= ========= ========= =========
# gets the first page of your Twitter timeline.
#
# avoids checking a tweet if it's 1) from you (you like yourself;
#   we get it) and 2) if it doesn't give enough tokens to make a
#   prediction.
sub read_list {
    my $config = shift ;
    my $nb     = shift ;
    my $list   = shift ;

    ...

    my @favorites ;
    my $timeline =     $twit->list_statuses({list_id => $list});

    for my $tweet (@$timeline) {
        my $id          = $tweet->{id} ;                          # twitter_id
        my $text        = $tweet->{text} ;                        # text
        my $created     = handle_date( $tweet->{created_at} ) ;   # created
        my $screen_name = $tweet->{user}->{screen_name} ;         # user id
        my $check       = toke( lc $text ) ;
        next if lc $screen_name eq lc $config->{user} ;
        next if !scalar keys %{ $check->{attributes} } ;
        my $r = $nb->predict( attributes => $check->{attributes} ) ;
        my $fav = int $r->{fave} * 100 ;
        next if $fav < $config->{limit} ;
        my $url = join '/', 'http:', '', 'twitter.com', $screen_name,
            'status', $id ;
        $tweet->{analysis} = $r ;
        $tweet->{gen_url}  = $url ;
        push @favorites, $tweet ;
        }

    return @favorites ;
    }

#========= ========= ========= ========= ========= ========= =========
sub train {

    my $nb = Algorithm::NaiveBayes->new( purge => 1 ) ;
    my $path = '/home/jacoby/.nb_twitter' ;

    # adapted on suggestion from Ken to

    # gets all tweets in your baseline table
    my $baseline = get_all() ;
    for my $entry (@$baseline) {
        my ( $tweet, $month, $year ) = (@$entry) ;
        my $label = join '', $year, ( sprintf '%02d', $month ) ;
        my $ham = toke(lc $tweet) ;
        next unless scalar keys %$ham ;
        $nb->add_instance(
            attributes => $ham->{attributes},
            label      => ['base'],
            ) ;
        }

    gets all tweets in your favorites table
    my $favorites = get_favorites() ;
    for my $entry (@$favorites) {
        my ( $tweet, $month, $year ) = (@$entry) ;
        my $label = join '', $year, ( sprintf '%02d', $month ) ;
        my $ham = toke(lc $tweet) ;
        next unless scalar keys %$ham ;
        $nb->add_instance(
            attributes => $ham->{attributes},
            label      => ['fave'],
            ) ;
        }

    $nb->train() ;
    return $nb ;
    }

#========= ========= ========= ========= ========= ========= =========
# tokenizes a tweet by breaking it into characters, removing URLs
# and short words
sub toke {
    my $tweet = shift ;
    my $ham ;
    my $tokenizer = String::Tokenizer->new() ;
    $tweet =~ s{https?://\S+}{}g ;
    $tokenizer->tokenize($tweet) ;

    for my $t ( $tokenizer->getTokens() ) {
        $t =~ s{\W}{}g ;
        next if length $t < 4 ;
        next if $t !~ /\D/ ;
        my @x = $tweet =~ m{($t)}gmix ;
        $ham->{attributes}{$t} = scalar @x ;
        }
    return $ham ;
    }


Honestly, String::Tokenizer is probably a bit too overkill for this, but I'll go with it for now. It might be better to get a list of the 100 or 500 most common words and exclude them from the tweets, instead of limiting by size. As is, strings like ada and sql would be excluded. But it's good for now.

We get a list of tweets including a number between 0 and 1, representing the likelihood, by Bayes, that I would like the tweet. In the end, it's turned into an integer between 0 and 100. You can also run this against your normal timeline to pull out tweets you would've liked but missed. I often do this

I run the follows_me version on occasion. So far, it is clear to me that the people I don't follow, I don't follow for a reason, and that remains valid.

If you use this and find value in it, please tell me below. Thanks and good coding.

2015/09/14

Thoughts on Machine Learning and Twitter Tools

I have a lot of Twitter data.

I decided a few months ago to get my list of those I follow and those following me, and then find that same information about each of them.

This took quite some time, as Twitter stops you from getting too much at a time.

I found a few interesting things. One was, of all the people I follow and all following me, I had the highest number of bi-directional connections (I follow them, they follow me; let's say "friends") of any, at something like 150.

But I'm thinking about writing the next step, and I gotta say, I don't wanna.

In part, that data's dead. It's Spring me, and my Spring followers. It's Fall now, and I don't know that the data is valid anymore.

So I'm thinking about the next step.

If "Big Data" is more than a buzzword, it is working with data that never ends. I mean, my lab has nodes in a huge research cluster, that contain like a quarter-TB of RAM. That's still a mind-boggling thing when I think about it. And it's not like we let that sit empty; we use it. Well, I don't. My part of the workflow is when the users bring their data and, increasingly, when it's done and being sent on. It clearly is large data sets being handled, but it isn't "Big Data", because we finish it, package it, and give it to our users.

"Big Data", it seems to me, means you find out more now than you did before, that you're always adding to it, because it never ends. "The Torture Never Stops", as Zappa said.

In the case of Twitter, I can get every tweet in my feed and every tweet I favorite. I could write that thing and have it run regularly, starting now. Questions: What do I do? How do I do it?

Here's the Why:

There's tools out there that I don't understand, and they are powerful tools. I want to understand them. I want to write things with these things that interest me, because it's an uncommon day that I'm interested in what I code most of the time.

Then there's Twitter. You can say many many things about Twitter, and it's largely true. But, there are interesting people talking about the interesting things they do, and and other people retweeting it. With luck, I can draw from that, create connections, learn things and do interesting things.

So, while there is the "make a thing because a thing" part, I'm hoping to use this.

So, the What:

A first step is to take my Favorites data and use it as "ham" in a Bayesian way to tell what kind of thing I like. I might need to come up with a mechanism beyond unfollowing to indicate what I don't want to see. Maybe do a search on "GamerGate", "Football" and "Cats"?

Anyway, once that's going, let it loose on my feed and have it bubble up tweets that it says I might favorite, that "fit the profile", so to speak. I'm thinking my next step is installing a Bayesian library like Algorithm::Bayesian or Algorithm::NaiveBayes, running my year+ worth of Twitter favorites as ham, and have it tell me once a day the things I should've favorited but didn't. Once I have that, I'll reorient and go from there.

2016/08/12

A Little Fun with OpenCV


I have a webcam that I have set to take pictures at work. Is this a long-term art project? Is it a classic Twitter over-share? An example of Quantified Self gone horribly, horribly wrong? I honestly don't know.

I do know that it's brought me into interesting places before. Getting my camera to work with V4L. Tweeting images. Determining if the screen is locked, both on Unity and Gnome. I've come to the point where I can set @hourly ~/bin/tweet_a_picture.pl. I don't, rather 0 9-18 * * 1-5 ~/bin/tweet_a_picture.pl, because if it's before 9am or on a weekend, I won't be here, so don't even try.

But if I'm pulled away, there are photos like the above. Who needs to see my space without me?

Yeah, who needs to see my space with me? We'll pass on that.

One of the members of Purdue Perl Mongers formerly worked with OpenCV, and I asked him to present on that topic this month. He even created a GitHub repo with his example code. It's Python, not Perl, which will be a digression for this blog, but not an unprecedented one. This gave me enough confidence in OpenCV and how it worked to create a program that uses it to tell if anyone's in the frame.

(The demo code suggested installing python-opencv for Ubuntu, to which I must add that the crucial opencv-data does not come with it. Just a warning.)

# webcam_here.py

# usage: webcam_here.py
# 1

# when runs, returns the number of upper bodies, and thus people, it currently sees

import cv2
# pass either device id (0 usually for webcam) or path to a video file
cam = cv2.VideoCapture(0)

# face cascade bundled with OpenCV
# classifier = cv2.CascadeClassifier('/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml')
classifier = cv2.CascadeClassifier('/usr/share/opencv/haarcascades/haarcascade_upperbody.xml')
# I found that haarcascade_upperbody did the best for identifying me.

# read in frame
retval, frame = cam.read()

# convert to grayscale
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# run classifier
results = classifier.detectMultiScale(
 frame_gray,
 # let's assume face will be close/big if in front of webcam
 minSize=(300,300)
)

# number of found upper bodies in webcam
print len(results)


And here it is in context.
# tweet_a_picture.pl
use 5.010 ;
use strict ;
use warnings ;
use IO::Interactive qw{ interactive } ;
use Net::Twitter ;
use YAML::XS qw{ DumpFile LoadFile } ;
use Getopt::Long ;

use lib '/home/jacoby/lib' ;
use Locked qw{ is_locked there };

my $override = 0 ;

GetOptions(
    'override' => \$override ,
    ) ;

my $config_file = $ENV{ HOME } . '/.twitter.cnf' ;
my $config      = LoadFile( $config_file ) ;

my $user = 'screen_name' ;
my $status = 'Just took a picture' ;

if ( is_locked() == 0 && there() == 1 || $override ) {
    say { interactive } 'unlocked' ;
    # highest supported resolution of my old-school webcam 
    # Twitter DOES have a 5MB limit on file size
    # https://dev.twitter.com/rest/media/uploading-media#imagerecs
    my $cam = q{/usr/bin/fswebcam -q -r 640x480 --jpeg 85 -D 1 --no-banner} ;
    my $time = time ;
    my $image = "/home/jacoby/Pictures/Self/shot-${time}.jpeg" ;
    qx{$cam $image} ;
    tweet_pic( $status , $image ) ;
    say { interactive } $time ;
    }

sub tweet_pic {
    my ( $status , $pic ) = @_ ;
    my ( $access_token, $access_token_secret ) ;
    ( $access_token, $access_token_secret ) = restore_tokens( $user ) ;
    my $twit = Net::Twitter->new(
        traits              => [qw/API::RESTv1_1/],
        consumer_key    => $config->{ consumer_key },
        consumer_secret => $config->{ consumer_secret },
        ssl => 1 ,
        ) ;

    if ( $access_token && $access_token_secret ) {
        $twit->access_token( $access_token ) ;
        $twit->access_token_secret( $access_token_secret ) ;
        }
    unless ( $twit->authorized ) {
        say qq{UNAUTHTORIZED} ; exit ;
        # I really handle this better
        # http://varlogrant.blogspot.com/2016/07/nettwitter-cookbook-how-to-tweet.html
        }

    my $img_ref ;
    my $file = $pic ;
    my ( $filename ) = reverse split m{/} , $pic ;
    push @$img_ref , $file ;
    push @$img_ref , $filename ;

    if ( $twit->update_with_media( $status , $img_ref  ) ) {
        no warnings ;
        say { interactive } $status ;
        }
    else {
        say { interactive } 'FAIL' ;
        }
    }

# is in Locked.pm, but placed here for simplicity
# there are issues with recognizing my webcam, which 
# we quash by sending STDERR to /dev/null
sub there {
    my $checker = '/home/jacoby/bin/webcam_here.py' ;
    return -1 if ! -f $checker ;
    my $o = qx{$checker 2> /dev/null} ;
    chomp $o ;
    return $o ;
    }

sub restore_tokens {
    my ( $user ) = @_ ;
    my ( $access_token, $access_token_secret ) ;
    if ( $config->{ tokens }{ $user } ) {
        $access_token = $config->{ tokens }{ $user }{ access_token } ;
        $access_token_secret =
            $config->{ tokens }{ $user }{ access_token_secret } ;
        }
    return $access_token, $access_token_secret ;
    }

sub save_tokens {
    my ( $user, $access_token, $access_token_secret ) = @_ ;
    $config->{ tokens }{ $user }{ access_token }        = $access_token ;
    $config->{ tokens }{ $user }{ access_token_secret } = $access_token_secret ;
    DumpFile( $config_file, $config ) ;
    return 1 ;
    }


Clearly, this is the start, it's currently very cargo-cult code, and it has several uses. A coming use is to go through my archive of pictures and identifying the ones where I'm gone. That'll either require me getting up to speed with Image::ObjectDetect or Python, but it's a thing I'm willing to do.

An interesting side-issue: I've found that the existing Haar cascades (the XML files that define a thing OpenCV can identify) do not like my glasses, and thus cannot identify my eyes or face with them on, thus, me using the upperbody cascade. I think I should train my own Haar classifier; I know I have enough pics of me for it.

2008/12/27

A pass at coding for Twitter and, kinda, Blogspot

So, you write something in your blog, and you want to tell somebody, right?

Me, too. And here, I've automated the process. This script, blogspot2jabber.pl, reads your Atom feed, looks to see if the newest post is new, and then sends a tweet to your Twitter feed.

I use AnyDBM_File to save the most recent, even though I don't nearly tap the awesome power of it, because opening it as a hash is a lot easier than reading then writing a file.

XML::Atom was the tough one to install. CPAN never did finish it, so I used apt-get to install it instead. The laugh is that XML is anything like human-readable.

And Net::Twitter just could not be easier.


#!/usr/bin/perl
# ========= ========= ========= ========= ========= =========
# blogspot2twitter.pl
#
# AUTHOR - Dave Jacoby
#
# Copyright (c) 2008. David Jacoby.
#
# This program is free software; you can redistribute it
# and/or modify it under the same terms as Perl itself.
#
# See http://www.perl.com/perl/misc/Artistic.html
#
# ---------

use 5.010 ;
use strict ;
use warnings ;
use AnyDBM_File ;
use Carp ;
use Net::Twitter ;
use XML::Atom::Client ;
use XML::Atom::Entry ;
use XML::Atom::Feed ;
use subs qw( get_entry send_to_twitter ) ;

dbmopen my %dbm, '/path/to/.database.dbm', 0600 or croak $! ;
if ( !defined $dbm{ 'current' } ) { $dbm{ 'current' } = '' ; }
say 'C ' . $dbm{ 'current' } ;

my $url = 'your_blogspot_Atom_ feed' ;
# I think that it'll autodetect if you just put the URL
my $entry = get_entry $url ;
my $text = 'New post to my blog - ' . $entry ;

if ( $entry ne $dbm{ 'current' } ) {
send_to_twitter $text ;
$dbm{ 'current' } = $entry ;
}

exit ;

# ===================================================================
sub get_entry {
my $url = shift ;
my $tweet_link ;
my $client = XML::Atom::Client->new ;
my $feed = $client->getFeed( $url ) ;
my @entries = $feed->entries ;

for my $link ( $entries[ 0 ]->link ) {
if ( $link->type eq 'text/html' ) {
$tweet_link = $link->href ;

#we should be picking it up 2nd pass
}
}
return $tweet_link ;
}

# -------------------------------------------------------------------

# ===================================================================
sub send_to_twitter {
my $user = 'username' ;
my $pass = 'password' ;
my $status = shift ;
my $tweet = Net::Twitter->new( username => $user,
password => $pass, ) ;
if ( $tweet->update( $status ) ) { say 'OK' ; }
else { say 'FAIL' ; }
}
# -------------------------------------------------------------------


I'd like to get the URL and subject in, but that can't be guaranteed to be under 140 characters.

2009/01/23

Testing A Syntax Highlighting Widget...

And it works!

I was considering making a thing that highlighted syntax and made pretty code samples. As this is my coding blog, I considered that essential.

Then I thought about code reuse and wheel reinvention, and I started looking for syntax highlighters.

Damien of Damien Learns Perl (which is a perfectly fine thing to blog, by the way) pointed me to FaziBear's Syntax Highlighter, as well as the code to add to make it work for Perl.

Thank you all.

I just typed "Thank you wall". So, thank you too, Larry Wall, for creating Perl in the first place.


#!/usr/bin/perl

use 5.010 ;
use IO::Interactive qw{ interactive } ;
use Net::Twitter ;

my $user = 'Ha!' ;
my $pass = 'DoubleHa!' ;
# REALLY SHOULD KICK THIS OUT TO A CONFIG FILE
my $status = join ' ', @ARGV ;

#There's a hard limit on the size of twits
#$status = substr $status , 0 , 140 ;
if ( length $status > 140 ) {
say { interactive } 'Too long' ;
exit ;
}

say $status ;

my $twit = Net::Twitter->new( username => $user, password => $pass ) ;

if ( $twit->update( $status ) ) {
say { interactive } 'OK' ;
} else {
say { interactive } 'FAIL' ;
}


This is a simple command line Perl program that posts to Twitter for you. I like graphical things for displaying my feed, but for writing, it's often more convenient to go to one of my dozens of terminals running bash and typing twitter.pl I\'m twittering from work right now. or maybe get_local_weather.pl | twitter.pl .

2016/09/04

Thoughts on ML Techniques to better handle Twitter

Thinking things through afk and thus not as polished as my normal posts.

Been thinking about grouping my friends (those I follow) strictly by relationship mapping. In part, I haven't done this because I can't read the math in the paper describing it, and in part because there are points where I serve as connecting node between two clusters and they have started interacting independently. I know a joy of Twitter is that it allows people to connect by interest and personality, not geography, but when a programmer in Washington and an activist in Indiana talk food and cats with each other, it makes my "programmer" cluster and my "Indiana" cluster less distinct.

So, what to do?

Topic Modelling.

I know about this via the Talking Machines podcast, and, without mathematic notation, if you take a body of text as a collection of words, the words it contains will vary by subject. If the topic is "politics", the text might contain "vote" and "veto" and "election" and "impeach". If the topic is "football", we'd see "lateral", "quarterback", "tackle" and "touchdown".

Rather than separating Twitter followers into groups simply by interactions, I could start with both certain lists I have curated (and yes, there are both "local tweeters" and "programmer" lists) and hashtags (because if you hashtag your tweet #perl, you likely are talking about Perl) to start to identify what words are more likely to come up when discussing certain subjects, then start adding then to those lists automatically.

If I can work this out 140 characters at a time.

2015/04/01

Thinking through Git in my work

This is mostly me thinking through some issues.

I use git and GitHub a fair amount. Not enough, not deeply enough, but a fair amount.

I understand Use Case #1, Compiled App, like RLangSyntax:

  • code in ~/dev/Project
  • subdirs (test/, lib/, examples/, docs/, etc) and documentation, license and build scripts in to git
  • git push origin master
  • compile code to binary, deploy the binary elsewhere (like GitHub's releases tab)
When someone wants to use this code, git clone Project gets you all you need to build it, except for compilers and associated libraries, which should be in README.md. (I forgot to put how to build into the RLangSyntax docs, then forgot how to build RLangSyntax. Forgive me, Admin, for I have sinned.)

Let's jump to Use Case #2, Perl Library, like Spark.pm.
  • code in ~/dev/Project
  • no build instructions; it's built
  • tests in ~/dev/Project/t/spark.t and the like (which this doesn't have, to my shame)
  • git push origin master
This is where I get confused. Eventually, this code needs to be in /production/lib, but you don't want to deploy using git pull or git clone, because you don't want /production/lib/Project/. Or, maybe you do and I just don't get it. Still, this is a case that I can do an acceptably wrong thing as required.

Use Case #3 is Perl command-line tools. We'll take on my twitter tool.
  • code in ~/dev/project
  • git push origin master
This begs about the same question as Perl Library. It could work to have ~production/bin/twitter.pl/twitter.pl, but then you have to expand your path to include every little thing. It gets more involved if you have libraries with executables in the repo, or the reverse, but let's get to the real hairball.

Use Case #4, the Hairball, is our web stuff.

  • ~web/ - document root
  • ~web/data - holds data that we want to make web-accessable
  • ~web/images - images for page headers and other universal things
  • ~web/lib - all our JavaScript
  • ~web/css - all our Cascading Style Sheets
  • ~web/cgi-bin - our server-side code
So, we might have the server-side code in ~web/cgi-bin/service/Project/foo.cgi, the client-side code in ~web/lib/Project/foo.js but maybe in ~web/lib/foo.js, the style in ~web/css/project.css and ~/web/css/service.css, and of course the libraries in ~production/lib/.

Maybe the key is to think of the ~web/lib and ~web/css as variations of Use Case #2, but the problem is that a lot of my JS code isn't general like the Perl code. I mean, wherever you want a sparkline, you can use Spark.pm, but the client code for ~/cgi-bin/service/Project/foo.cgi is going to mostly be very specific to foo.cgi except for the jQuery, Mustache and a few other things that are in use across services and projects.

A possible solution, having things be in ~web/service, with the JSON APIs in ~web/cgi-bin/service/apis and the JavaScript in either ~web/service/lib or ~/web/lib depending on how universal it is, but we lose certain abilities. I certainly have written CGI code which mostly puts out as little HTML as it needs to get the JS, which works for the small audience those tools need.

I mostly code foo.js in relation to foo.cgi or foo.html, but making tests and breaking it into pieces may keep me from having KLOC-size libraries I hate to work on in the future.

And here, we have departed from git into project and web architecture, and into best practices. Still, any suggestions would be gladly accepted.

2012/11/19

Social Media and Me

Facebook: I tend to keep that friends-and-family, which means, if you're one of my Facebook friends, you are more than likely someone I have met face-to-face. Because of this, things that I post there, I am unlikely to post elsewhere, and vice versa. My friends and family have very little interest in the totality of my interests.

Google Plus: By and large, this is where I post my geekery, and where I follow my geekery. My go-to groups are:

  • Purdue, which is filled with IT/programming people in the Purdue area
  • Android, which is filled with people developing or reviewing apps written for Android
  • Arduino, which is filled with people developing things based around Arduino
  • Lafayette Tech Labs, which is filled with people who go to Lafayette Tech Labs, which is an attempt to create a hackerspace in Greater Lafayette, and has much overlap with the above
I use a script I wrote called Plus2RSS to export my public posts (which is most of my posts) to ...

Twitter: The core. For just about anything I post, anywhere but Facebook, it ends up on Twitter. I could use Buffer to focus it all in Twitter "Prime Time" (10am-4pm local time) but I won't. I'm vaguely open to having a real-me feed and an automated, copy-from-blogs and such feed.

2010/08/27

A Squeeze of Python to give me a Buzz

I've been learning Python.

I know, I know....

It starts with Google Buzz. I accept web pages and window apps as the only choices to see social media output from places like Identica, Twitter, Facebook, etc. But my preference is to type things on the command line for most of my content-generating needs. If I wanted to send the same thought to most of my microblogging sites, I just pipe it together.

echo My oh-so-crucial thought | twitter.pl | identi.pl | wiki.pl

So far, two things have been standing in my way. Facebook and Google Buzz. I have yet to find ways to use my beloved Perl to write to either. But I poked around and found some sample code on the Google Buzz API page. Specifically, I found make_post.py, which does write to Buzz, but it will not easily integrate to my preferred way. Specifically, it takes in your key and secret as command-line flags and accepts your Buzz update via STDIN. If that's how you roll, that's fine, but for me, the message is join ' ' , @ARGV . So, I recoded it, taking more out than I put in, and using some of my very slight knowledge of Python, to make this. I have the key and secret hard-coded, which is kinda acceptable for personal use but still not to be accepted. You should use the make_post.py that comes with buzz-python-client to set up your OAuth if you use this, because anything that would do that has been brutally ripped from the original code.

Expect Perl code based on this to come around eventually. For now, enjoy buzz-python-client and my addition.

# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys
import traceback
import getopt

#
# Load Buzz library (if available...)
#

try:
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import buzz
except ImportError:
print 'Error importing Buzz library!!!'

token = verification_code = buzz_client = ''

#
# Function to obtain login data
#

def GetLoginData():
'Obtains login information from either the command line or by querying the user'

global token, verification_code, buzz_client
key = 'KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY'
secret = 'SECRETSECRETSECRETSECRETSECRETSECRET'
buzz_client.build_oauth_access_token(key, secret)

#
# Main program starts here
#

try:
buzz_client = buzz.Client()
buzz_client.oauth_scopes=[buzz.FULL_ACCESS_SCOPE]
buzz_client.use_anonymous_oauth_consumer()
GetLoginData()
args = sys.argv
del args[0]
message = ' '.join(args)
message = message.strip()
post = buzz.Post(content=message)
buzz_client.create_post(post)
print message
except:
print '\nBzzzz! Something broke!!!'
print '-' * 50
traceback.print_exc()
print '-' * 50

2011/01/14

Graphical Programming with Yahoo Pipes

Let us start with the need. I found a tool to allow me to scroll RSS feeds across my desktop, just like every news channel.  This is a neat thing, but I found that I didn't like the way it did RSS feeds. Instead of cycling through them, it kept using one. So, I set about solving that by combining feeds.

The method I chose was Yahoo Pipes. When I decided to check Craigslist for items of certain types (Fender guitars, especially) in local areas (where I decided I could reasonably drive, should the catch of a lifetime come around), I used Pipes, mostly as an excuse to learn it. With Google Reader being the recipient of those feeds, I didn't need to massage them too much. The news I fed News was twitter feeds from news sites I wanted to follow, mostly local news and weather.

To the right, we see the "code" I used to combine seven news sources, (mostly) remove redundancies, do some slight editing to the content and ensure that only the day's news shows up. It's all Twitter RSS, so there's one date format, but that format is not usably sortable. I format today's date in the same format and filter out if it isn't there, but the better solution is to take a Unix timestamp of the current time and date (representing seconds since Jan. 1, 1970), subtract 24*24*60 (number of seconds in a day) and drop out entries that were not published during that time. That will take a slightly larger amount of cleverness to do that, but I think I'll crack that nut eventually.

The thing I notice is, while a graphical solution seems tailor-made to mimic flowcharts, there's no splitting here. It's all very linear, with minor loops and filters. I suppose I could get a more funnel-looking program if I handled the inputs separately. The more I play with Yahoo Pipes, the more I like it.

2009/10/20

Perl IS a community!

Ever have code that doesn't work and you don't know why? I do on occasion, and the error messages didn't tell me anything useful, so I decided to try Perl::Critic on it. This is less a tool to debug your non-functioning program than a program that tells you a better way to have your functioning program function, but I thought it could tell me something.

I generally start all my programs these days with:

use Modern::Perl ;
This takes the place of this:

use 5.010 ;
use strict ;
use warnings ;
This is very nice, in that it gets you to the cool stuff in Perl in one line.

But...

Perl::Critic wants you to run using strict and warnings, and when you use Modern::Perl, it doesn't explicitly say that you are using them, and Critic dinks you for it.

I thought that was interesting.

And, today, when you find something interesting, you tweet it. I also put it on Identi.ca, which is like twitter but mostly programmer types go there. And I got a response. From chromatic on identi.ca and from the Critic folks on Twitter.

That just warms my heart. Not specifically the help to make it work, which is of course great, but that the developers are listening.

And, incidently, making a .perlcritic file with this in it makes it work:

equivalent_modules = Modern::Perl



2016/11/19

Graphs are not that Scary!



As with most things I blog about, this starts with Twitter. I follow a lot of people on Twitter, and I use Lists. I want to be able to group people more-or-less on community, because there's the community where they talk about programming, for example, and the community where they talk about music, or the town I live in.

I can begin to break things up myself, but curation is a hard thing, so I wanted to do it automatically. And I spent a long time not knowing what to do. I imagined myself traversing trees in what looks like linked lists reimagined by Cthulhu, and that doesn't sound like much fun at all.

Eventually, I decided to search on "graphs and Perl". Of course, I probably should've done it earlier, but oh well. I found Graph. I had used GD::Graph before, which is a plotting library. (There has to be some index of how overloaded words are.) And once I installed it, I figured it out: As a programmer, all you're dealing with are arrays and hashes. Nothing scary.

Word Ladder


We'll take a problem invented by Lewis Carroll called a "word ladder", where you find your way from one word (for example, "cold") to another ("warm") by changing one letter at a time:

    cold
    coRd
    cArd
    Ward
    warM

Clearly, this can and is often done by hand, but if you're looking to automate it, there are three basic problems: what are the available words, how do you determine when words are one change away, and how do you do this to get the provable shortest path?

First, I went to CERIAS years ago and downloaded word lists. Computer security researchers use them because real words are bad passwords, so, lists of real words can be used to create rainbow tables and the like. My lists are years old, so there may be new words I don't account for, but unlike Lewis Carroll, I can get from APE to MAN in five words, not six.

    ape
    apS
    aAs
    Mas
    maN

Not sure that Lewis Carroll would've accepted AAS, but there you go

There is a term for the number of changes it takes to go from one word to another, and it's called the Levenshtein Distance. I first learned about this from perlbrew, which is how, if you type "perlbrew isntall", it guesses that you meant to type "perlbrew install". It's hardcoded there because perlbrew can't assume you have anything but perl and core modules. I use the function from perlbrew instead of Text::Levenshtein but it is a module worth looking into.

And the final answer is "Put it into a graph and use Dijkstra's Algorithm!"

Perhaps not with the exclamation point.

Showing Code


Here's making a graph of it:

#!/usr/bin/env perl

use feature qw{say} ;
use strict ;
use warnings ;

use Data::Dumper ;
use Graph ;
use List::Util qw{min} ;
use Storable ;

for my $l ( 3 .. 16 ) {
    create_word_graph($l) ;
    }
exit ;

# -------------------------------------------------------------------
# we're creating a word graph of all words that are of length $length
# where the nodes are all words and the edges are unweighted, because
# they're all weighted 1. No connection between "foo" and "bar" because 
# the distance is "3".

sub create_word_graph {
    my $length = shift ;
    my %dict = get_words($length) ;
    my @dict = sort keys %dict ; # sorting probably is unnecessary
    my $g    = Graph->new() ;

    # compare each word to each word. If the distance is 1, put it
    # into the graph. This implementation is O(N**2) but probably
    # could be redone as O(NlogN), but I didn't care to.

    for my $i ( @dict ) {
        for my $j ( @dict ) {
            my $dist = editdist( $i, $j ) ;
            if ( $dist == 1 ) {
                $g->add_edge( $i, $j ) ;
                }
            }
        }

    # Because I'm using Storable to store the Graph object for use
    # later, I only use this once. But, I found there's an endian
    # issue if you try to open Linux-generated Storable files in
    # Strawberry Perl.

    store $g , "/home/jacoby/.word_$length.store" ;
    }

# -------------------------------------------------------------------
# this is where we get the words and only get words of the correct
# length. I have a number of dictionary files, and I put them in
# a hash to de-duplicate them.

sub get_words {
    my $length = shift ;
    my %output ;
    for my $d ( glob( '/home/jacoby/bin/Toys/Dict/*' ) ) {
        if ( open my $fh, '<', $d ) {
            for my $l ( <$fh> ) {
                chomp $l ;
                $l =~ s/\s//g ;
                next if length $l != $length ;
                next if $l =~ /\W/ ;
                next if $l =~ /\d/ ;
                $output{ uc $l }++ ;
                }
            }
        }
    return %output ;
    }

# -------------------------------------------------------------------
# straight copy of Wikipedia's "Levenshtein Distance", straight taken
# from perlbrew. If I didn't have this, I'd probably use 
# Text::Levenshtein.

sub editdist {
    my ( $f, $g ) = @_ ;
    my @a = split //, $f ;
    my @b = split //, $g ;

    # There is an extra row and column in the matrix. This is the
    # distance from the empty string to a substring of the target.
    my @d ;
    $d[ $_ ][ 0 ] = $_ for ( 0 .. @a ) ;
    $d[ 0 ][ $_ ] = $_ for ( 0 .. @b ) ;

    for my $i ( 1 .. @a ) {
        for my $j ( 1 .. @b ) {
            $d[ $i ][ $j ] = (
                  $a[ $i - 1 ] eq $b[ $j - 1 ]
                ? $d[ $i - 1 ][ $j - 1 ]
                : 1 + min( $d[ $i - 1 ][ $j ], $d[ $i ][ $j - 1 ], $d[ $i - 1 ][ $j - 1 ] )
                ) ;
            }
        }

    return $d[ @a ][ @b ] ;
    }

Following are what my wordlists can do. Something tells me that, when we get to 16-letter words, it's more a bunch of disconnected nodes than a graph.

1718 3-letter words
6404 4-letter words
13409 5-letter words
20490 6-letter words
24483 7-letter words
24295 8-letter words
19594 9-letter words
13781 10-letter words
8792 11-letter words
5622 12-letter words
3349 13-letter words
1851 14-letter words
999 15-letter words
514 16-letter words

My solver isn't perfect, and the first thing I'd want to add is ensuring that both the starting and ending words are actually in the word list. Without that, your code goes on forever.

So, I won't show off the whole program below, but it does use Storable, Graph and feature qw{say}.

dijkstra( $graph , 'foo' , 'bar' ) ;

# -------------------------------------------------------------------
# context-specific perl implementation of Dijkstra's Algorithm for
# shortest-path

sub dijkstra {
    my ( $graph, $source, $target, ) = @_ ;

    # the graph pre-exists and is passed in 
    # $source is 'foo', the word we're starting from
    # $target is 'bar', the word we're trying to get to

    my @q ; # will be the list of all words
    my %dist ; # distance from source. $dist{$source} will be zero 
    my %prev ; # this holds our work being every edge of the tree
               # we're pulling from the graph. 

    # we set the the distance for every node to basically infinite, then 
    # for the starting point to zero

    for my $v ( $graph->unique_vertices ) {
        $dist{$v} = 1_000_000_000 ;    # per Wikipeia, infinity
        push @q, $v ;
        }
    $dist{$source} = 0 ;

LOOP: while (@q) {

        # resort, putting words with short distances first
        # first pass being $source , LONG WAY AWAY

        @q = sort { $dist{$a} <=> $dist{$b} } @q ;
        my $u = shift @q ;

        # say STDERR join "\t", $u, $dist{$u} ;

        # here, we end the first time we see the target.
        # we COULD get a list of every path that's the shortest length,
        # but that's not what we're doing here

        last LOOP if $u eq $target ;

        # this is a complex and unreadable way of ensuring that
        # we're only getting edges that contain $u, which is the 
        # word we're working on right now

        for my $e (
            grep {
                my @a = @$_ ;
                grep {/^${u}$/} @a
            } $graph->unique_edges
            ) {

            # $v is the word on the other end of the edge
            # $w is the distance, which is 1 because of the problem
            # $alt is the new distance between $source and $v, 
            # replacing the absurdly high number set before

            my ($v) = grep { $_ ne $u } @$e ;
            my $w   = 1 ;
            my $alt = $dist{$u} + $w ;
            if ( $alt < $dist{$v} ) {
                $dist{$v} = $alt ;
                $prev{$v} = $u ;
                }
            }
        }

    my @nodes = $graph->unique_vertices ;
    my @edges = $graph->unique_edges ;
    return {
        distances => \%dist,
        previous  => \%prev,
        nodes     => \@nodes,
        edges     => \@edges,
        } ;
    }

I return lots of stuff, but the part really necessary is %prev, because that, $source and $target are everything you need. Assuming we're trying to go from FOR to FAR, a number of words will satisfy $prev{FOR}, but it's the one we're wanting. In the expanded case of FOO to BAR, $prev->{BAR} = 'FAR', $prev->{FAR} is 'FOR', and $prev->{FOR} is 'FOO'.

And nothing in there is complex. It's all really hashes or arrays or values. Nothing a programmer should have any problem with.

CPAN has a number of other modules of use: Graph::Dijkstra has that algorithm already written, and Graph::D3 allows you to create a graph in such a way that you can use it in D3.js. Plus, there are a number of modules in Algorithm::* that do good and useful things. So go in, start playing with it. It's deep, there are weeds, but it isn't scary.