package WebService::Beeminder;
# ABSTRACT: Access the Beeminder API
use 5.010;
use strict;
use warnings;
use MooseX::Method::Signatures;
use Moose;
use JSON::Any;
use LWP::UserAgent;
use Carp qw(croak);
our $VERSION = '0.001'; # VERSION: Generated by DZP::OurPkg:Version
has 'token' => (isa => 'Str', is => 'ro', required => 1);
has 'username'=> (isa => 'Str', is => 'ro', default => 'me');
has 'agent' => ( is => 'rw'); # Must act like LWP::UserAgent
has 'dryrun' => (isa => 'Bool',is => 'ro', default => 0);
has 'apibase' => (isa => 'Str', is => 'ro', default => 'https://www.beeminder.com/api/v1');
# Everything needs to be able to read/write JSON.
my $json = JSON::Any->new;
sub BUILD {
my ($self) = @_;
# Make sure we have a user-agent, if none provided.
if (not $self->agent) {
$self->agent(LWP::UserAgent->new(agent => "perl/$], WebService::Beeminder/" . $self->VERSION));
}
return;
}
# Get information about a user
# TODO: Accept optional parameters
method user(Str $user = "me") {
# AFAIK, the $user here is irrelevant, since we can only query
# the user we're logged in as. Still, we'll respect it, in
# case that changes in the future.
return $self->_get('users',"$user.json");
}
# Gets the datapoints for a goal
# DONE: 2011-11-25. This takes no parameters.
method datapoints(Str $goal) {
return $self->_userget( 'goals', $goal, 'datapoints.json');
}
method add_datapoint(
Str :$goal!,
Int :$timestamp, # TODO: Change to a proper timestamp type.
Num :$value!,
Str :$comment = "",
Bool :$sendmail = 0
) {
$timestamp //= time();
return $self->_userpost(
{ timestamp => $timestamp, value => $value, comment => $comment, sendmail => $sendmail },
'goals', $goal, 'datapoints.json'
);
}
# Posts to the API. Takes a hashref of parameters. Remaining arguments
# are interpreted as a path.
sub _userpost {
my ($self, $params, @path) = @_;
my $url = $self->_path('users', $self->username, @path);
my $resp = $self->agent->post( $url, $params );
unless ($resp->is_success) {
croak "Failed to fetch $url - ".$resp->status_line;
}
return $json->decode($resp->content);
};
# Builds a path, and adds appropriate auth tokens, etc.
sub _path {
my ($self, @path) = @_;
my $url = join('/', $self->apibase, @path) . "?auth_token=" . $self->token;
if ($self->dryrun) {
$url .= "&dryrun=1";
}
return $url;
}
# Fetches something from the API. Automatically prepends the API path,
# adds the token to the end, and decodes the JSON.
sub _get {
my ($self, @path) = @_;
my $url = $self->_path(@path);
my $resp = $self->agent->get( $url );
unless ($resp->is_success) {
croak "Failed to fetch $url - ".$resp->status_line;
}
return $json->decode($resp->content);
}
# As for _get, but prepends 'users' and the current user.
sub _userget {
my ($self, @args) = @_;
return $self->_get('users', $self->username, @args);
}
1;
__END__
=pod
=head1 NAME
WebService::Beeminder - Access the Beeminder API
=head1 VERSION
version 0.001
=head1 SYNOPSIS
my $bee = WebService::Beeminder->new( token => $token );
# I flossed my teeth today.
$bee->add_datapoint( goal => 'floss', value => 1 );
# When did I last take dance lessons?
my $result = $bee->datapoints('dance');
say "I danced $result->[-1]{timestamp} seconds from the epoch at " .
$result->[-1]{comment};
=head1 DESCRIPTION
This is a I<thin-ish> wrapper around the Beeminder API. All results are
exactly what's returned by the underlying API, with the JSON being
converted into Perl data structures.
You need a Beeminder API token to use this module. The easiest way
to get a personal token is just to login to L<Beeminder|http://beeminder.com/>
and then go to L<https://www.beeminder.com/api/v1/auth_token.json>.
Copy'n'paste the token into your code (or a config file your code uses),
and you're good to go!
More information on tokens is available in the
L<Beeminder API documentation|http://beeminder.com/api>.
=head1 METHODS
=head2 user
my $result = $bee->user();
Obtains information about the current user. This returns a user resource
(as defined by the Beeminder API), which looks like this:
{
username => "alice",
timezone => "America/Los_Angeles",
updated_at => 1343449880,
goals => ['gmailzero', 'weight']
}
Note: Presently only basic parameters are returned, even though the
beeminder API supports additional filters.
=head2 datapoints
my $results = $bee->datapoints($goal);
This method returns an array reference of data points for the given goal:
[
{
id => 'abc123'
timestamp => 1234567890,
value => 1.1,
comment => "Frobnicated a widget",
updated_at => 1234567890
},
{
id => 'abc124'
timestamp => 1234567891,
value => 1.2,
comment => "Straightened my doohickies",
updated_at => 1234567891
},
]
=head2 add_datapoint
my $point = $bee->add_datapoint(
goal => 'floss',
timestamp => time(), # Optional, defaults to now
value => 1,
comment => 'Floss every tooth for great justice!',
sendmail => 0, # Optional, defaults to false
);
Adds a data-point to the given goal. Mail will be sent to the user if
the C<sendmail> parameter is true.
Returns the data-point that was created:
{
id => 'abc125'
timestamp => 1234567892,
value => 1,
comment => 'Floss every tooth for great justice!'
updated_at => 1234567892
}
=head1 INSTALLATION
This module presently uses L<MooseX::Method::Signatures>. If you're
not experienced in installing module dependencies, it's recommend you
use L<APP::cpanminus>, which doesn't require any special privileges
or software.
Perl v5.10.0 or later is required for this module.
=head1 SEE ALSO
=over
=item *
L<The Beeminder API|http://beeminder.com/api>
=back
=for Pod::Coverage BUILD
=head1 AUTHOR
Paul Fenwick <pjf@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Paul Fenwick.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut