App-BarnesNoble-WishListMinder

 view release on metacpan or  search on metacpan

lib/App/BarnesNoble/WishListMinder.pm  view on Meta::CPAN

  my $dbh = DBI->connect("dbi:SQLite:dbname=$fn","","",
                         { AutoCommit => 0, PrintError => 0, RaiseError => 1,
                           sqlite_unicode => 1 });

  $self->create_database_schema($dbh) unless $exists;

  $dbh;
} # end _build_dbh

sub close_dbh
{
  my $self = shift;

  if ($self->has_dbh) {
    my $dbh = $self->dbh;
    $dbh->rollback;
    $dbh->disconnect;
    $self->_clear_dbh;
  }
} # end close_dbh

has scraper => qw(is lazy);
sub _build_scraper {
  require Web::Scraper::BarnesNoble::WishList;

  Web::Scraper::BarnesNoble::WishList::bn_scraper();
} # end _build_scraper

has updates => qw(is ro  default) => sub { {} };

#---------------------------------------------------------------------
sub configure
{
  my ($self) = @_;

  my $config_file = $self->config_file;

  say "Your config file is:\n $config_file";

  unless ($config_file->is_file) {
    die "$config_file is a directory!\n" if $config_file->is_dir;
    $config_file->spew_utf8(<<'END CONFIG');
;						-*-conf-windows-*-
; Your credentials for logging in to the Barnes & Noble website go here:
email    = YOUR EMAIL HERE
password = YOUR PASSWORD HERE

; If you want the Price Drop Alert emails to go to a different address,
; uncomment the next line and set the email address.
;report   = EMAIL ADDRESS FOR ALERTS

; Next, you need one or more wishlists to monitor.
; Each wishlist must have a unique name in [brackets].

[My Wishlist]
wishlist = WISHLIST URL HERE
END CONFIG
    say "\nYou need to replace the ALL CAPS placeholders with the correct values.";
  }

  if (my $editor = $ENV{VISUAL} || $ENV{EDITOR}) {
    require Text::ParseWords;
    system(Text::ParseWords::shellwords($editor), "$config_file");
  }
} # end configure

#---------------------------------------------------------------------
sub create_database_schema
{
  my ($self, $dbh) = @_;

  $dbh->do("PRAGMA foreign_keys = ON");

  $dbh->do(<<'');
CREATE TABLE books (
  ean         INTEGER PRIMARY KEY,
  title       TEXT NOT NULL,
  author      TEXT
)

  $dbh->do(<<'');
CREATE TABLE wishlists (
  wishlist_id   INTEGER PRIMARY KEY,
  url           TEXT NOT NULL UNIQUE,
  last_fetched  TIMESTAMP
)

  $dbh->do(<<'');
CREATE TABLE wishlist_books (
  wishlist_id   INTEGER NOT NULL REFERENCES wishlists,
  ean           INTEGER NOT NULL REFERENCES books,
  priority      INTEGER,
  date_added    DATE NOT NULL DEFAULT CURRENT_DATE,
  date_removed  DATE,
  PRIMARY KEY (wishlist_id,ean)
)

  $dbh->do(<<'');
CREATE TABLE prices (
  ean            INTEGER NOT NULL REFERENCES books,
  first_recorded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  last_checked   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  current        TINYINT NOT NULL DEFAULT 1,
  price          INTEGER,
  list_price     INTEGER,
  discount       INTEGER,
  PRIMARY KEY (ean,first_recorded)
)

  $dbh->commit;

} # end create_database_schema

#---------------------------------------------------------------------
sub login
{
  my ($self) = shift;

  my ($config, $m) = ($self->config->{_}, $self->mech);

  $m->get('https://www.barnesandnoble.com/signin');

  #path("/tmp/login.html")->spew_utf8($m->content);

lib/App/BarnesNoble/WishListMinder.pm  view on Meta::CPAN

sub have_user_cookie
{
  my $have_cookie;
  my $min_expires = time() + 30;

  shift->mech->cookie_jar->scan(sub {
    $have_cookie = 1 if $_[1] eq 'userid'
                    and $_[4] eq '.barnesandnoble.com'
                    and $_[8] > $min_expires
  });

  $have_cookie;
} # end have_user_cookie

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

sub update_wishlists
{
  my $self = shift;

  my $config  = $self->config;
  my $m       = $self->mech;

  # Ensure we can open the database before we start making web requests
  $self->dbh;

  $self->login unless $self->have_user_cookie;

  for my $wishlist (sort keys %$config) {
    next if $wishlist eq '_';   # the root INI section

    my $response = $m->get( $config->{$wishlist}{wishlist} );
    my $books    = $self->scrape_response($response);
    unless (@$books) {
      warn "$config->{$wishlist}{wishlist} has no entries\n";
      # Save the response for debugging:
      $self->dir->child("empty-$wishlist.html")->spew_utf8($response->content);
    }
#    path("/tmp/wishlist.html")->spew_utf8($response->content);
    $self->write_db($config->{$wishlist}{wishlist}, $response->last_modified // $response->date, $books);
  }
} # end update_wishlists

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

sub usage {
  my $name = $0;
  $name =~ s!^.*[/\\]!!;

  shift->close_dbh;

  print "$name $VERSION\n";
  exit if $_[0] and $_[0] eq 'version';
  print <<"END USAGE";
\nUsage:  $name [options] [EAN_or_TITLE_or_AUTHOR] ...
  -a, --all-history        Show price history even when multiple items match
  -e, --email              Send Price Drop Alert email (implies --update)
  -q, --quiet              Don't print list of updates
  -s, --since=DATE         Print books whose price changed on or after DATE
  -u, --update             Download current prices from wishlist
      --configure          Create and/or edit the config file
      --help               Display this help message
      --version            Display version information
END USAGE

    exit;
} # end usage
#---------------------------------------------------------------------

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

  # Process command line options
  my ($all_history, $fetch_wishlist, $quiet, $send_email, $since_date);
  {
    require Getopt::Long; Getopt::Long->VERSION(2.24); # object-oriented
    my $getopt = Getopt::Long::Parser->new(
      config => [qw(bundling no_getopt_compat)]
    );
    my $usage = sub { $self->usage(@_) };

    $getopt->getoptionsfromarray(\@args,
      'all-history|a' => \$all_history,
      'email|e'   => \$send_email,
      'quiet|q'   => \$quiet,
      'since|s=s' => \$since_date,
      'update|u'  => \$fetch_wishlist,
      'configure' => sub { $self->configure; exit },
      'help'      => $usage,
      'version'   => $usage
    ) or $self->usage;
  }

  # Update database & send email if requested
  if ($fetch_wishlist or $send_email) {
    $self->update_wishlists;

    $self->email_price_drop_alert if $send_email;
    $self->print_updates unless $quiet;
  } elsif (not @args and not $since_date) {
    # Didn't fetch updates and no request to display book data
    if ($self->config_file->is_file) {
      $self->usage;
    } else {
      $self->configure;
    }
  }

  if ($since_date) {
    $self->print_updates_since($since_date);
  }

  # Display data from the database about requested books
  foreach my $arg (@args) {
    if ($arg =~ /^[0-9]{13}\z/) {
      $self->print_price_history($arg);
    } else {
      $self->print_matching_books($arg, $all_history);
    }
  }



( run in 6.499 seconds using v1.01-cache-2.11-cpan-c221a9de4ec )